| /* |
| * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "webrtc/voice_engine/file_recorder.h" |
| |
| #include <list> |
| |
| #include "webrtc/audio/utility/audio_frame_operations.h" |
| #include "webrtc/common_audio/resampler/include/resampler.h" |
| #include "webrtc/common_types.h" |
| #include "webrtc/modules/include/module_common_types.h" |
| #include "webrtc/modules/media_file/media_file.h" |
| #include "webrtc/modules/media_file/media_file_defines.h" |
| #include "webrtc/rtc_base/logging.h" |
| #include "webrtc/rtc_base/platform_thread.h" |
| #include "webrtc/system_wrappers/include/event_wrapper.h" |
| #include "webrtc/typedefs.h" |
| #include "webrtc/voice_engine/coder.h" |
| |
| namespace webrtc { |
| |
| namespace { |
| |
| // The largest decoded frame size in samples (60ms with 32kHz sample rate). |
| enum { MAX_AUDIO_BUFFER_IN_SAMPLES = 60 * 32 }; |
| enum { MAX_AUDIO_BUFFER_IN_BYTES = MAX_AUDIO_BUFFER_IN_SAMPLES * 2 }; |
| enum { kMaxAudioBufferQueueLength = 100 }; |
| |
| class FileRecorderImpl : public FileRecorder { |
| public: |
| FileRecorderImpl(uint32_t instanceID, FileFormats fileFormat); |
| ~FileRecorderImpl() override; |
| |
| // FileRecorder functions. |
| int32_t RegisterModuleFileCallback(FileCallback* callback) override; |
| FileFormats RecordingFileFormat() const override; |
| int32_t StartRecordingAudioFile(const char* fileName, |
| const CodecInst& codecInst, |
| uint32_t notificationTimeMs) override; |
| int32_t StartRecordingAudioFile(OutStream* destStream, |
| const CodecInst& codecInst, |
| uint32_t notificationTimeMs) override; |
| int32_t StopRecording() override; |
| bool IsRecording() const override; |
| int32_t codec_info(CodecInst* codecInst) const override; |
| int32_t RecordAudioToFile(const AudioFrame& frame) override; |
| |
| private: |
| int32_t WriteEncodedAudioData(const int8_t* audioBuffer, size_t bufferLength); |
| |
| int32_t SetUpAudioEncoder(); |
| |
| uint32_t _instanceID; |
| FileFormats _fileFormat; |
| MediaFile* _moduleFile; |
| |
| CodecInst codec_info_; |
| int8_t _audioBuffer[MAX_AUDIO_BUFFER_IN_BYTES]; |
| AudioCoder _audioEncoder; |
| Resampler _audioResampler; |
| }; |
| |
| FileRecorderImpl::FileRecorderImpl(uint32_t instanceID, FileFormats fileFormat) |
| : _instanceID(instanceID), |
| _fileFormat(fileFormat), |
| _moduleFile(MediaFile::CreateMediaFile(_instanceID)), |
| codec_info_(), |
| _audioBuffer(), |
| _audioEncoder(instanceID), |
| _audioResampler() {} |
| |
| FileRecorderImpl::~FileRecorderImpl() { |
| MediaFile::DestroyMediaFile(_moduleFile); |
| } |
| |
| FileFormats FileRecorderImpl::RecordingFileFormat() const { |
| return _fileFormat; |
| } |
| |
| int32_t FileRecorderImpl::RegisterModuleFileCallback(FileCallback* callback) { |
| if (_moduleFile == NULL) { |
| return -1; |
| } |
| return _moduleFile->SetModuleFileCallback(callback); |
| } |
| |
| int32_t FileRecorderImpl::StartRecordingAudioFile(const char* fileName, |
| const CodecInst& codecInst, |
| uint32_t notificationTimeMs) { |
| if (_moduleFile == NULL) { |
| return -1; |
| } |
| codec_info_ = codecInst; |
| int32_t retVal = 0; |
| retVal = _moduleFile->StartRecordingAudioFile(fileName, _fileFormat, |
| codecInst, notificationTimeMs); |
| |
| if (retVal == 0) { |
| retVal = SetUpAudioEncoder(); |
| } |
| if (retVal != 0) { |
| LOG(LS_WARNING) << "Failed to initialize file " << fileName |
| << " for recording."; |
| |
| if (IsRecording()) { |
| StopRecording(); |
| } |
| } |
| return retVal; |
| } |
| |
| int32_t FileRecorderImpl::StartRecordingAudioFile(OutStream* destStream, |
| const CodecInst& codecInst, |
| uint32_t notificationTimeMs) { |
| codec_info_ = codecInst; |
| int32_t retVal = _moduleFile->StartRecordingAudioStream( |
| *destStream, _fileFormat, codecInst, notificationTimeMs); |
| |
| if (retVal == 0) { |
| retVal = SetUpAudioEncoder(); |
| } |
| if (retVal != 0) { |
| LOG(LS_WARNING) << "Failed to initialize outStream for recording."; |
| |
| if (IsRecording()) { |
| StopRecording(); |
| } |
| } |
| return retVal; |
| } |
| |
| int32_t FileRecorderImpl::StopRecording() { |
| memset(&codec_info_, 0, sizeof(CodecInst)); |
| return _moduleFile->StopRecording(); |
| } |
| |
| bool FileRecorderImpl::IsRecording() const { |
| return _moduleFile->IsRecording(); |
| } |
| |
| int32_t FileRecorderImpl::RecordAudioToFile( |
| const AudioFrame& incomingAudioFrame) { |
| if (codec_info_.plfreq == 0) { |
| LOG(LS_WARNING) << "RecordAudioToFile() recording audio is not " |
| << "turned on."; |
| return -1; |
| } |
| AudioFrame tempAudioFrame; |
| tempAudioFrame.samples_per_channel_ = 0; |
| if (incomingAudioFrame.num_channels_ == 2 && !_moduleFile->IsStereo()) { |
| // Recording mono but incoming audio is (interleaved) stereo. |
| tempAudioFrame.num_channels_ = 1; |
| tempAudioFrame.sample_rate_hz_ = incomingAudioFrame.sample_rate_hz_; |
| tempAudioFrame.samples_per_channel_ = |
| incomingAudioFrame.samples_per_channel_; |
| if (!incomingAudioFrame.muted()) { |
| AudioFrameOperations::StereoToMono( |
| incomingAudioFrame.data(), incomingAudioFrame.samples_per_channel_, |
| tempAudioFrame.mutable_data()); |
| } |
| } else if (incomingAudioFrame.num_channels_ == 1 && _moduleFile->IsStereo()) { |
| // Recording stereo but incoming audio is mono. |
| tempAudioFrame.num_channels_ = 2; |
| tempAudioFrame.sample_rate_hz_ = incomingAudioFrame.sample_rate_hz_; |
| tempAudioFrame.samples_per_channel_ = |
| incomingAudioFrame.samples_per_channel_; |
| if (!incomingAudioFrame.muted()) { |
| AudioFrameOperations::MonoToStereo( |
| incomingAudioFrame.data(), incomingAudioFrame.samples_per_channel_, |
| tempAudioFrame.mutable_data()); |
| } |
| } |
| |
| const AudioFrame* ptrAudioFrame = &incomingAudioFrame; |
| if (tempAudioFrame.samples_per_channel_ != 0) { |
| // If ptrAudioFrame is not empty it contains the audio to be recorded. |
| ptrAudioFrame = &tempAudioFrame; |
| } |
| |
| // Encode the audio data before writing to file. Don't encode if the codec |
| // is PCM. |
| // NOTE: stereo recording is only supported for WAV files. |
| // TODO(hellner): WAV expect PCM in little endian byte order. Not |
| // "encoding" with PCM coder should be a problem for big endian systems. |
| size_t encodedLenInBytes = 0; |
| if (_fileFormat == kFileFormatPreencodedFile || |
| STR_CASE_CMP(codec_info_.plname, "L16") != 0) { |
| if (_audioEncoder.Encode(*ptrAudioFrame, _audioBuffer, |
| &encodedLenInBytes) == -1) { |
| LOG(LS_WARNING) << "RecordAudioToFile() codec " << codec_info_.plname |
| << " not supported or failed to encode stream."; |
| return -1; |
| } |
| } else { |
| size_t outLen = 0; |
| _audioResampler.ResetIfNeeded(ptrAudioFrame->sample_rate_hz_, |
| codec_info_.plfreq, |
| ptrAudioFrame->num_channels_); |
| // TODO(yujo): skip resample if frame is muted. |
| _audioResampler.Push( |
| ptrAudioFrame->data(), |
| ptrAudioFrame->samples_per_channel_ * ptrAudioFrame->num_channels_, |
| reinterpret_cast<int16_t*>(_audioBuffer), MAX_AUDIO_BUFFER_IN_BYTES, |
| outLen); |
| encodedLenInBytes = outLen * sizeof(int16_t); |
| } |
| |
| // Codec may not be operating at a frame rate of 10 ms. Whenever enough |
| // 10 ms chunks of data has been pushed to the encoder an encoded frame |
| // will be available. Wait until then. |
| if (encodedLenInBytes) { |
| if (WriteEncodedAudioData(_audioBuffer, encodedLenInBytes) == -1) { |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| int32_t FileRecorderImpl::SetUpAudioEncoder() { |
| if (_fileFormat == kFileFormatPreencodedFile || |
| STR_CASE_CMP(codec_info_.plname, "L16") != 0) { |
| if (_audioEncoder.SetEncodeCodec(codec_info_) == -1) { |
| LOG(LS_ERROR) << "SetUpAudioEncoder() codec " << codec_info_.plname |
| << " not supported."; |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| int32_t FileRecorderImpl::codec_info(CodecInst* codecInst) const { |
| if (codec_info_.plfreq == 0) { |
| return -1; |
| } |
| *codecInst = codec_info_; |
| return 0; |
| } |
| |
| int32_t FileRecorderImpl::WriteEncodedAudioData(const int8_t* audioBuffer, |
| size_t bufferLength) { |
| return _moduleFile->IncomingAudioData(audioBuffer, bufferLength); |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<FileRecorder> FileRecorder::CreateFileRecorder( |
| uint32_t instanceID, |
| FileFormats fileFormat) { |
| return std::unique_ptr<FileRecorder>( |
| new FileRecorderImpl(instanceID, fileFormat)); |
| } |
| |
| } // namespace webrtc |