| /* | 
 |  *  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/base/logging.h" | 
 | #include "webrtc/base/platform_thread.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/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_; | 
 |     for (size_t i = 0; i < (incomingAudioFrame.samples_per_channel_); i++) { | 
 |       // Sample value is the average of left and right buffer rounded to | 
 |       // closest integer value. Note samples can be either 1 or 2 byte. | 
 |       tempAudioFrame.data_[i] = ((incomingAudioFrame.data_[2 * i] + | 
 |                                   incomingAudioFrame.data_[(2 * i) + 1] + 1) >> | 
 |                                  1); | 
 |     } | 
 |   } 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_; | 
 |     for (size_t i = 0; i < (incomingAudioFrame.samples_per_channel_); i++) { | 
 |       // Duplicate sample to both channels | 
 |       tempAudioFrame.data_[2 * i] = incomingAudioFrame.data_[i]; | 
 |       tempAudioFrame.data_[2 * i + 1] = incomingAudioFrame.data_[i]; | 
 |     } | 
 |   } | 
 |  | 
 |   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_); | 
 |     _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 |