/* * 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 #include "modules/media_file/media_file_impl.h" #include "rtc_base/format_macros.h" #include "rtc_base/logging.h" #include "system_wrappers/include/file_wrapper.h" namespace webrtc { MediaFile* MediaFile::CreateMediaFile(const int32_t id) { return new MediaFileImpl(id); } void MediaFile::DestroyMediaFile(MediaFile* module) { delete static_cast(module); } MediaFileImpl::MediaFileImpl(const int32_t id) : _id(id), _ptrFileUtilityObj(NULL), codec_info_(), _ptrInStream(NULL), _ptrOutStream(NULL), _fileFormat((FileFormats)-1), _recordDurationMs(0), _playoutPositionMs(0), _notificationMs(0), _playingActive(false), _recordingActive(false), _isStereo(false), _openFile(false), _fileName(), _ptrCallback(NULL) { LOG(LS_INFO) << "MediaFileImpl()"; codec_info_.plname[0] = '\0'; _fileName[0] = '\0'; } MediaFileImpl::~MediaFileImpl() { LOG(LS_INFO) << "~MediaFileImpl()"; { rtc::CritScope lock(&_crit); if(_playingActive) { StopPlaying(); } if(_recordingActive) { StopRecording(); } delete _ptrFileUtilityObj; if(_openFile) { delete _ptrInStream; _ptrInStream = NULL; delete _ptrOutStream; _ptrOutStream = NULL; } } } int64_t MediaFileImpl::TimeUntilNextProcess() { LOG(LS_WARNING) << "TimeUntilNextProcess: This method is not used by MediaFile class."; return -1; } void MediaFileImpl::Process() { LOG(LS_WARNING) << "Process: This method is not used by MediaFile class."; } int32_t MediaFileImpl::PlayoutAudioData(int8_t* buffer, size_t& dataLengthInBytes) { LOG(LS_INFO) << "MediaFileImpl::PlayoutData(buffer= " << static_cast(buffer) << ", bufLen= " << dataLengthInBytes << ")"; const size_t bufferLengthInBytes = dataLengthInBytes; dataLengthInBytes = 0; if(buffer == NULL || bufferLengthInBytes == 0) { LOG(LS_ERROR) << "Buffer pointer or length is NULL!"; return -1; } int32_t bytesRead = 0; { rtc::CritScope lock(&_crit); if(!_playingActive) { LOG(LS_WARNING) << "Not currently playing!"; return -1; } if(!_ptrFileUtilityObj) { LOG(LS_ERROR) << "Playing, but no FileUtility object!"; StopPlaying(); return -1; } switch(_fileFormat) { case kFileFormatPcm48kHzFile: case kFileFormatPcm32kHzFile: case kFileFormatPcm16kHzFile: case kFileFormatPcm8kHzFile: bytesRead = _ptrFileUtilityObj->ReadPCMData( *_ptrInStream, buffer, bufferLengthInBytes); break; case kFileFormatCompressedFile: bytesRead = _ptrFileUtilityObj->ReadCompressedData( *_ptrInStream, buffer, bufferLengthInBytes); break; case kFileFormatWavFile: bytesRead = _ptrFileUtilityObj->ReadWavDataAsMono( *_ptrInStream, buffer, bufferLengthInBytes); break; case kFileFormatPreencodedFile: bytesRead = _ptrFileUtilityObj->ReadPreEncodedData( *_ptrInStream, buffer, bufferLengthInBytes); if(bytesRead > 0) { dataLengthInBytes = static_cast(bytesRead); return 0; } break; default: { LOG(LS_ERROR) << "Invalid file format: " << _fileFormat; assert(false); break; } } if( bytesRead > 0) { dataLengthInBytes = static_cast(bytesRead); } } HandlePlayCallbacks(bytesRead); return 0; } void MediaFileImpl::HandlePlayCallbacks(int32_t bytesRead) { bool playEnded = false; uint32_t callbackNotifyMs = 0; if(bytesRead > 0) { // Check if it's time for PlayNotification(..). _playoutPositionMs = _ptrFileUtilityObj->PlayoutPositionMs(); if(_notificationMs) { if(_playoutPositionMs >= _notificationMs) { _notificationMs = 0; callbackNotifyMs = _playoutPositionMs; } } } else { // If no bytes were read assume end of file. StopPlaying(); playEnded = true; } // Only _callbackCrit may and should be taken when making callbacks. rtc::CritScope lock(&_callbackCrit); if(_ptrCallback) { if(callbackNotifyMs) { _ptrCallback->PlayNotification(_id, callbackNotifyMs); } if(playEnded) { _ptrCallback->PlayFileEnded(_id); } } } int32_t MediaFileImpl::PlayoutStereoData( int8_t* bufferLeft, int8_t* bufferRight, size_t& dataLengthInBytes) { LOG(LS_INFO) << "MediaFileImpl::PlayoutStereoData(Left = " << static_cast(bufferLeft) << ", Right = " << static_cast(bufferRight) << ", Len= " << dataLengthInBytes << ")"; const size_t bufferLengthInBytes = dataLengthInBytes; dataLengthInBytes = 0; if(bufferLeft == NULL || bufferRight == NULL || bufferLengthInBytes == 0) { LOG(LS_ERROR) << "A buffer pointer or the length is NULL!"; return -1; } bool playEnded = false; uint32_t callbackNotifyMs = 0; { rtc::CritScope lock(&_crit); if(!_playingActive || !_isStereo) { LOG(LS_WARNING) << "Not currently playing stereo!"; return -1; } if(!_ptrFileUtilityObj) { LOG(LS_ERROR) << "Playing stereo, but the FileUtility objects is NULL!"; StopPlaying(); return -1; } // Stereo playout only supported for WAV files. int32_t bytesRead = 0; switch(_fileFormat) { case kFileFormatWavFile: bytesRead = _ptrFileUtilityObj->ReadWavDataAsStereo( *_ptrInStream, bufferLeft, bufferRight, bufferLengthInBytes); break; default: LOG(LS_ERROR) << "Trying to read non-WAV as stereo audio (not supported)"; break; } if(bytesRead > 0) { dataLengthInBytes = static_cast(bytesRead); // Check if it's time for PlayNotification(..). _playoutPositionMs = _ptrFileUtilityObj->PlayoutPositionMs(); if(_notificationMs) { if(_playoutPositionMs >= _notificationMs) { _notificationMs = 0; callbackNotifyMs = _playoutPositionMs; } } } else { // If no bytes were read assume end of file. StopPlaying(); playEnded = true; } } rtc::CritScope lock(&_callbackCrit); if(_ptrCallback) { if(callbackNotifyMs) { _ptrCallback->PlayNotification(_id, callbackNotifyMs); } if(playEnded) { _ptrCallback->PlayFileEnded(_id); } } return 0; } int32_t MediaFileImpl::StartPlayingAudioFile( const char* fileName, const uint32_t notificationTimeMs, const bool loop, const FileFormats format, const CodecInst* codecInst, const uint32_t startPointMs, const uint32_t stopPointMs) { if(!ValidFileName(fileName)) { return -1; } if(!ValidFileFormat(format,codecInst)) { return -1; } if(!ValidFilePositions(startPointMs,stopPointMs)) { return -1; } // Check that the file will play longer than notificationTimeMs ms. if((startPointMs && stopPointMs && !loop) && (notificationTimeMs > (stopPointMs - startPointMs))) { LOG(LS_ERROR) << "specified notification time is longer than amount of" << " ms that will be played"; return -1; } FileWrapper* inputStream = FileWrapper::Create(); if(inputStream == NULL) { LOG(LS_INFO) << "Failed to allocate input stream for file " << fileName; return -1; } if (!inputStream->OpenFile(fileName, true)) { delete inputStream; LOG(LS_ERROR) << "Could not open input file " << fileName; return -1; } if(StartPlayingStream(*inputStream, loop, notificationTimeMs, format, codecInst, startPointMs, stopPointMs) == -1) { inputStream->CloseFile(); delete inputStream; return -1; } rtc::CritScope lock(&_crit); _openFile = true; strncpy(_fileName, fileName, sizeof(_fileName)); _fileName[sizeof(_fileName) - 1] = '\0'; return 0; } int32_t MediaFileImpl::StartPlayingAudioStream( InStream& stream, const uint32_t notificationTimeMs, const FileFormats format, const CodecInst* codecInst, const uint32_t startPointMs, const uint32_t stopPointMs) { return StartPlayingStream(stream, false, notificationTimeMs, format, codecInst, startPointMs, stopPointMs); } int32_t MediaFileImpl::StartPlayingStream( InStream& stream, bool loop, const uint32_t notificationTimeMs, const FileFormats format, const CodecInst* codecInst, const uint32_t startPointMs, const uint32_t stopPointMs) { if(!ValidFileFormat(format,codecInst)) { return -1; } if(!ValidFilePositions(startPointMs,stopPointMs)) { return -1; } rtc::CritScope lock(&_crit); if(_playingActive || _recordingActive) { LOG(LS_ERROR) << "StartPlaying called, but already playing or recording file " << ((_fileName[0] == '\0') ? "(name not set)" : _fileName); return -1; } if(_ptrFileUtilityObj != NULL) { LOG(LS_ERROR) << "StartPlaying called, but FileUtilityObj already exists!"; StopPlaying(); return -1; } _ptrFileUtilityObj = new ModuleFileUtility(); if(_ptrFileUtilityObj == NULL) { LOG(LS_INFO) << "Failed to create FileUtilityObj!"; return -1; } switch(format) { case kFileFormatWavFile: { if(_ptrFileUtilityObj->InitWavReading(stream, startPointMs, stopPointMs) == -1) { LOG(LS_ERROR) << "Not a valid WAV file!"; StopPlaying(); return -1; } _fileFormat = kFileFormatWavFile; break; } case kFileFormatCompressedFile: { if(_ptrFileUtilityObj->InitCompressedReading(stream, startPointMs, stopPointMs) == -1) { LOG(LS_ERROR) << "Not a valid Compressed file!"; StopPlaying(); return -1; } _fileFormat = kFileFormatCompressedFile; break; } case kFileFormatPcm8kHzFile: case kFileFormatPcm16kHzFile: case kFileFormatPcm32kHzFile: case kFileFormatPcm48kHzFile: { // ValidFileFormat() called in the beginneing of this function // prevents codecInst from being NULL here. assert(codecInst != NULL); if(!ValidFrequency(codecInst->plfreq) || _ptrFileUtilityObj->InitPCMReading(stream, startPointMs, stopPointMs, codecInst->plfreq) == -1) { LOG(LS_ERROR) << "Not a valid raw 8 or 16 KHz PCM file!"; StopPlaying(); return -1; } _fileFormat = format; break; } case kFileFormatPreencodedFile: { // ValidFileFormat() called in the beginneing of this function // prevents codecInst from being NULL here. assert(codecInst != NULL); if(_ptrFileUtilityObj->InitPreEncodedReading(stream, *codecInst) == -1) { LOG(LS_ERROR) << "Not a valid PreEncoded file!"; StopPlaying(); return -1; } _fileFormat = kFileFormatPreencodedFile; break; } default: { LOG(LS_ERROR) << "Invalid file format: " << format; assert(false); break; } } if(_ptrFileUtilityObj->codec_info(codec_info_) == -1) { LOG(LS_ERROR) << "Failed to retrieve codec info!"; StopPlaying(); return -1; } _isStereo = (codec_info_.channels == 2); if(_isStereo && (_fileFormat != kFileFormatWavFile)) { LOG(LS_WARNING) << "Stereo is only allowed for WAV files"; StopPlaying(); return -1; } _playingActive = true; _playoutPositionMs = _ptrFileUtilityObj->PlayoutPositionMs(); _ptrInStream = &stream; _notificationMs = notificationTimeMs; return 0; } int32_t MediaFileImpl::StopPlaying() { rtc::CritScope lock(&_crit); _isStereo = false; if(_ptrFileUtilityObj) { delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; } if(_ptrInStream) { // If MediaFileImpl opened the InStream it must be reclaimed here. if(_openFile) { delete _ptrInStream; _openFile = false; } _ptrInStream = NULL; } codec_info_.pltype = 0; codec_info_.plname[0] = '\0'; if(!_playingActive) { LOG(LS_WARNING) << "playing is not active!"; return -1; } _playingActive = false; return 0; } bool MediaFileImpl::IsPlaying() { LOG(LS_VERBOSE) << "MediaFileImpl::IsPlaying()"; rtc::CritScope lock(&_crit); return _playingActive; } int32_t MediaFileImpl::IncomingAudioData( const int8_t* buffer, const size_t bufferLengthInBytes) { LOG(LS_INFO) << "MediaFile::IncomingData(buffer= " << static_cast(buffer) << ", bufLen= " << bufferLengthInBytes << ")"; if(buffer == NULL || bufferLengthInBytes == 0) { LOG(LS_ERROR) << "Buffer pointer or length is NULL!"; return -1; } bool recordingEnded = false; uint32_t callbackNotifyMs = 0; { rtc::CritScope lock(&_crit); if(!_recordingActive) { LOG(LS_WARNING) << "Not currently recording!"; return -1; } if(_ptrOutStream == NULL) { LOG(LS_ERROR) << "Recording is active, but output stream is NULL!"; assert(false); return -1; } int32_t bytesWritten = 0; uint32_t samplesWritten = codec_info_.pacsize; if(_ptrFileUtilityObj) { switch(_fileFormat) { case kFileFormatPcm8kHzFile: case kFileFormatPcm16kHzFile: case kFileFormatPcm32kHzFile: case kFileFormatPcm48kHzFile: bytesWritten = _ptrFileUtilityObj->WritePCMData( *_ptrOutStream, buffer, bufferLengthInBytes); // Sample size is 2 bytes. if(bytesWritten > 0) { samplesWritten = bytesWritten/sizeof(int16_t); } break; case kFileFormatCompressedFile: bytesWritten = _ptrFileUtilityObj->WriteCompressedData( *_ptrOutStream, buffer, bufferLengthInBytes); break; case kFileFormatWavFile: bytesWritten = _ptrFileUtilityObj->WriteWavData( *_ptrOutStream, buffer, bufferLengthInBytes); if(bytesWritten > 0 && STR_NCASE_CMP(codec_info_.plname, "L16", 4) == 0) { // Sample size is 2 bytes. samplesWritten = bytesWritten/sizeof(int16_t); } break; case kFileFormatPreencodedFile: bytesWritten = _ptrFileUtilityObj->WritePreEncodedData( *_ptrOutStream, buffer, bufferLengthInBytes); break; default: LOG(LS_ERROR) << "Invalid file format: " << _fileFormat; assert(false); break; } } else { // TODO (hellner): quick look at the code makes me think that this // code is never executed. Remove? if(_ptrOutStream) { if(_ptrOutStream->Write(buffer, bufferLengthInBytes)) { bytesWritten = static_cast(bufferLengthInBytes); } } } _recordDurationMs += samplesWritten / (codec_info_.plfreq / 1000); // Check if it's time for RecordNotification(..). if(_notificationMs) { if(_recordDurationMs >= _notificationMs) { _notificationMs = 0; callbackNotifyMs = _recordDurationMs; } } if(bytesWritten < (int32_t)bufferLengthInBytes) { LOG(LS_WARNING) << "Failed to write all requested bytes!"; StopRecording(); recordingEnded = true; } } // Only _callbackCrit may and should be taken when making callbacks. rtc::CritScope lock(&_callbackCrit); if(_ptrCallback) { if(callbackNotifyMs) { _ptrCallback->RecordNotification(_id, callbackNotifyMs); } if(recordingEnded) { _ptrCallback->RecordFileEnded(_id); return -1; } } return 0; } int32_t MediaFileImpl::StartRecordingAudioFile( const char* fileName, const FileFormats format, const CodecInst& codecInst, const uint32_t notificationTimeMs, const uint32_t maxSizeBytes) { if(!ValidFileName(fileName)) { return -1; } if(!ValidFileFormat(format,&codecInst)) { return -1; } FileWrapper* outputStream = FileWrapper::Create(); if(outputStream == NULL) { LOG(LS_INFO) << "Failed to allocate memory for output stream"; return -1; } if (!outputStream->OpenFile(fileName, false)) { delete outputStream; LOG(LS_ERROR) << "Could not open output file '" << fileName << "' for writing!"; return -1; } if(maxSizeBytes) { outputStream->SetMaxFileSize(maxSizeBytes); } if(StartRecordingAudioStream(*outputStream, format, codecInst, notificationTimeMs) == -1) { outputStream->CloseFile(); delete outputStream; return -1; } rtc::CritScope lock(&_crit); _openFile = true; strncpy(_fileName, fileName, sizeof(_fileName)); _fileName[sizeof(_fileName) - 1] = '\0'; return 0; } int32_t MediaFileImpl::StartRecordingAudioStream( OutStream& stream, const FileFormats format, const CodecInst& codecInst, const uint32_t notificationTimeMs) { // Check codec info if(!ValidFileFormat(format,&codecInst)) { return -1; } rtc::CritScope lock(&_crit); if(_recordingActive || _playingActive) { LOG(LS_ERROR) << "StartRecording called, but already recording or playing file " << _fileName << "!"; return -1; } if(_ptrFileUtilityObj != NULL) { LOG(LS_ERROR) << "StartRecording called, but fileUtilityObj already exists!"; StopRecording(); return -1; } _ptrFileUtilityObj = new ModuleFileUtility(); if(_ptrFileUtilityObj == NULL) { LOG(LS_INFO) << "Cannot allocate fileUtilityObj!"; return -1; } CodecInst tmpAudioCodec; memcpy(&tmpAudioCodec, &codecInst, sizeof(CodecInst)); switch(format) { case kFileFormatWavFile: { if(_ptrFileUtilityObj->InitWavWriting(stream, codecInst) == -1) { LOG(LS_ERROR) << "Failed to initialize WAV file!"; delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } _fileFormat = kFileFormatWavFile; break; } case kFileFormatCompressedFile: { // Write compression codec name at beginning of file if(_ptrFileUtilityObj->InitCompressedWriting(stream, codecInst) == -1) { LOG(LS_ERROR) << "Failed to initialize Compressed file!"; delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } _fileFormat = kFileFormatCompressedFile; break; } case kFileFormatPcm8kHzFile: case kFileFormatPcm16kHzFile: case kFileFormatPcm32kHzFile: case kFileFormatPcm48kHzFile: { if(!ValidFrequency(codecInst.plfreq) || _ptrFileUtilityObj->InitPCMWriting(stream, codecInst.plfreq) == -1) { LOG(LS_ERROR) << "Failed to initialize PCM file!"; delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } _fileFormat = format; break; } case kFileFormatPreencodedFile: { if(_ptrFileUtilityObj->InitPreEncodedWriting(stream, codecInst) == -1) { LOG(LS_ERROR) << "Failed to initialize Pre-Encoded file!"; delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } _fileFormat = kFileFormatPreencodedFile; break; } default: { LOG(LS_ERROR) << "Invalid file format " << format << " specified!"; delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } } _isStereo = (tmpAudioCodec.channels == 2); if(_isStereo) { if(_fileFormat != kFileFormatWavFile) { LOG(LS_WARNING) << "Stereo is only allowed for WAV files"; StopRecording(); return -1; } if((STR_NCASE_CMP(tmpAudioCodec.plname, "L16", 4) != 0) && (STR_NCASE_CMP(tmpAudioCodec.plname, "PCMU", 5) != 0) && (STR_NCASE_CMP(tmpAudioCodec.plname, "PCMA", 5) != 0)) { LOG(LS_WARNING) << "Stereo is only allowed for codec PCMU, PCMA and L16 "; StopRecording(); return -1; } } memcpy(&codec_info_, &tmpAudioCodec, sizeof(CodecInst)); _recordingActive = true; _ptrOutStream = &stream; _notificationMs = notificationTimeMs; _recordDurationMs = 0; return 0; } int32_t MediaFileImpl::StopRecording() { rtc::CritScope lock(&_crit); if(!_recordingActive) { LOG(LS_WARNING) << "recording is not active!"; return -1; } _isStereo = false; if(_ptrFileUtilityObj != NULL) { // Both AVI and WAV header has to be updated before closing the stream // because they contain size information. if((_fileFormat == kFileFormatWavFile) && (_ptrOutStream != NULL)) { _ptrFileUtilityObj->UpdateWavHeader(*_ptrOutStream); } delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; } if(_ptrOutStream != NULL) { // If MediaFileImpl opened the OutStream it must be reclaimed here. if(_openFile) { delete _ptrOutStream; _openFile = false; } _ptrOutStream = NULL; } _recordingActive = false; codec_info_.pltype = 0; codec_info_.plname[0] = '\0'; return 0; } bool MediaFileImpl::IsRecording() { LOG(LS_VERBOSE) << "MediaFileImpl::IsRecording()"; rtc::CritScope lock(&_crit); return _recordingActive; } int32_t MediaFileImpl::RecordDurationMs(uint32_t& durationMs) { rtc::CritScope lock(&_crit); if(!_recordingActive) { durationMs = 0; return -1; } durationMs = _recordDurationMs; return 0; } bool MediaFileImpl::IsStereo() { LOG(LS_VERBOSE) << "MediaFileImpl::IsStereo()"; rtc::CritScope lock(&_crit); return _isStereo; } int32_t MediaFileImpl::SetModuleFileCallback(FileCallback* callback) { rtc::CritScope lock(&_callbackCrit); _ptrCallback = callback; return 0; } int32_t MediaFileImpl::FileDurationMs(const char* fileName, uint32_t& durationMs, const FileFormats format, const uint32_t freqInHz) { if(!ValidFileName(fileName)) { return -1; } if(!ValidFrequency(freqInHz)) { return -1; } ModuleFileUtility* utilityObj = new ModuleFileUtility(); if(utilityObj == NULL) { LOG(LS_ERROR) << "failed to allocate utility object!"; return -1; } const int32_t duration = utilityObj->FileDurationMs(fileName, format, freqInHz); delete utilityObj; if(duration == -1) { durationMs = 0; return -1; } durationMs = duration; return 0; } int32_t MediaFileImpl::PlayoutPositionMs(uint32_t& positionMs) const { rtc::CritScope lock(&_crit); if(!_playingActive) { positionMs = 0; return -1; } positionMs = _playoutPositionMs; return 0; } int32_t MediaFileImpl::codec_info(CodecInst& codecInst) const { rtc::CritScope lock(&_crit); if(!_playingActive && !_recordingActive) { LOG(LS_ERROR) << "Neither playout nor recording has been initialized!"; return -1; } if (codec_info_.pltype == 0 && codec_info_.plname[0] == '\0') { LOG(LS_ERROR) << "The CodecInst for " << (_playingActive ? "Playback" : "Recording") << " is unknown!"; return -1; } memcpy(&codecInst,&codec_info_,sizeof(CodecInst)); return 0; } bool MediaFileImpl::ValidFileFormat(const FileFormats format, const CodecInst* codecInst) { if(codecInst == NULL) { if(format == kFileFormatPreencodedFile || format == kFileFormatPcm8kHzFile || format == kFileFormatPcm16kHzFile || format == kFileFormatPcm32kHzFile || format == kFileFormatPcm48kHzFile) { LOG(LS_ERROR) << "Codec info required for file format specified!"; return false; } } return true; } bool MediaFileImpl::ValidFileName(const char* fileName) { if((fileName == NULL) ||(fileName[0] == '\0')) { LOG(LS_ERROR) << "FileName not specified!"; return false; } return true; } bool MediaFileImpl::ValidFilePositions(const uint32_t startPointMs, const uint32_t stopPointMs) { if(startPointMs == 0 && stopPointMs == 0) // Default values { return true; } if(stopPointMs &&(startPointMs >= stopPointMs)) { LOG(LS_ERROR) << "startPointMs must be less than stopPointMs!"; return false; } if(stopPointMs &&((stopPointMs - startPointMs) < 20)) { LOG(LS_ERROR) << "minimum play duration for files is 20 ms!"; return false; } return true; } bool MediaFileImpl::ValidFrequency(const uint32_t frequency) { if((frequency == 8000) || (frequency == 16000)|| (frequency == 32000) || (frequency == 48000)) { return true; } LOG(LS_ERROR) << "Frequency should be 8000, 16000, 32000, or 48000 (Hz)"; return false; } } // namespace webrtc