/* * 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 #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/system_wrappers/include/logging.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(_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::CreateFileRecorder( uint32_t instanceID, FileFormats fileFormat) { return std::unique_ptr( new FileRecorderImpl(instanceID, fileFormat)); } } // namespace webrtc