/* * Copyright (c) 2011 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 "critical_section_wrapper.h" #include "file_wrapper.h" #include "media_file_impl.h" #include "tick_util.h" #include "trace.h" #if (defined(WIN32) || defined(WINCE)) #define STR_CASE_CMP _stricmp #define STR_NCASE_CMP _strnicmp #else #define STR_CASE_CMP strcasecmp #define STR_NCASE_CMP strncasecmp #endif namespace webrtc { MediaFile* MediaFile::CreateMediaFile(const WebRtc_Word32 id) { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, id, "CreateMediaFile()"); return new MediaFileImpl(id); } void MediaFile::DestroyMediaFile(MediaFile* module) { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, -1, "DestroyMediaFile()"); delete static_cast(module); } MediaFileImpl::MediaFileImpl(const WebRtc_Word32 id) : _id(id), _crit(*CriticalSectionWrapper::CreateCriticalSection()), _callbackCrit(*CriticalSectionWrapper::CreateCriticalSection()), _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) { WEBRTC_TRACE(kTraceMemory, kTraceFile, id, "Created"); codec_info_.plname[0] = '\0'; _fileName[0] = '\0'; } MediaFileImpl::~MediaFileImpl() { WEBRTC_TRACE(kTraceMemory, kTraceFile, _id, "~MediaFileImpl()"); { CriticalSectionScoped lock(_crit); if(_playingActive) { StopPlaying(); } if(_recordingActive) { StopRecording(); } delete _ptrFileUtilityObj; if(_openFile) { delete _ptrInStream; _ptrInStream = NULL; delete _ptrOutStream; _ptrOutStream = NULL; } } delete &_crit; delete &_callbackCrit; } WebRtc_Word32 MediaFileImpl::Version(WebRtc_Word8* version, WebRtc_UWord32& remainingBufferInBytes, WebRtc_UWord32& position) const { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "Version(?,bufferLength:%d,position:%d)", remainingBufferInBytes, position); return GetVersion(version, remainingBufferInBytes, position); } WebRtc_Word32 MediaFile::GetVersion(WebRtc_Word8* version, WebRtc_UWord32& remainingBufferInBytes, WebRtc_UWord32& position) { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, -1, "GetVersion(?,bufferLength:%d,position:%d)", remainingBufferInBytes, position); if(version == NULL) { WEBRTC_TRACE(kTraceWarning, kTraceFile, -1, "Invalid buffer pointer in argument to Version()"); return -1; } const WebRtc_Word8 ourVersion[] = "MediaFile 1.0.0"; const WebRtc_UWord32 ourLength =(WebRtc_UWord32)strlen(ourVersion); if(remainingBufferInBytes <(ourLength + 1)) { return -1; } memcpy(&version[position], ourVersion, ourLength); version[position + ourLength] = '\0'; remainingBufferInBytes -=(ourLength + 1); position +=(ourLength + 1); return 0; } WebRtc_Word32 MediaFileImpl::ChangeUniqueId(const WebRtc_Word32 id) { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "ChangeUniqueId(new id:%d)", id); _id = id; return 0; } WebRtc_Word32 MediaFileImpl::TimeUntilNextProcess() { WEBRTC_TRACE( kTraceWarning, kTraceFile, _id, "TimeUntilNextProcess: This method is not used by MediaFile class."); return -1; } WebRtc_Word32 MediaFileImpl::Process() { WEBRTC_TRACE(kTraceWarning, kTraceFile, _id, "Process: This method is not used by MediaFile class."); return -1; } WebRtc_Word32 MediaFileImpl::PlayoutAVIVideoData( WebRtc_Word8* buffer, WebRtc_UWord32& dataLengthInBytes) { return PlayoutData( buffer, dataLengthInBytes, true); } WebRtc_Word32 MediaFileImpl::PlayoutAudioData(WebRtc_Word8* buffer, WebRtc_UWord32& dataLengthInBytes) { return PlayoutData( buffer, dataLengthInBytes, false); } WebRtc_Word32 MediaFileImpl::PlayoutData(WebRtc_Word8* buffer, WebRtc_UWord32& dataLengthInBytes, bool video) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "MediaFileImpl::PlayoutData(buffer= 0x%x, bufLen= %ld)", buffer, dataLengthInBytes); const WebRtc_UWord32 bufferLengthInBytes = dataLengthInBytes; dataLengthInBytes = 0; if(buffer == NULL || bufferLengthInBytes == 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Buffer pointer or length is NULL!"); return -1; } WebRtc_Word32 bytesRead = 0; { CriticalSectionScoped lock(_crit); if(!_playingActive) { WEBRTC_TRACE(kTraceWarning, kTraceFile, _id, "Not currently playing!"); return -1; } if(!_ptrFileUtilityObj) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Playing, but no FileUtility object!"); StopPlaying(); return -1; } switch(_fileFormat) { 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 = bytesRead; return 0; } break; #ifdef WEBRTC_MODULE_UTILITY_VIDEO case kFileFormatAviFile: { if(video) { bytesRead = _ptrFileUtilityObj->ReadAviVideoData( buffer, bufferLengthInBytes); } else { bytesRead = _ptrFileUtilityObj->ReadAviAudioData( buffer, bufferLengthInBytes); } break; } #endif default: WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Playing file, but file format invalid!"); assert(false); break; } if( bytesRead > 0) { dataLengthInBytes =(WebRtc_UWord32) bytesRead; } } HandlePlayCallbacks(bytesRead); return 0; } void MediaFileImpl::HandlePlayCallbacks(WebRtc_Word32 bytesRead) { bool playEnded = false; WebRtc_UWord32 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. CriticalSectionScoped lock(_callbackCrit); if(_ptrCallback) { if(callbackNotifyMs) { _ptrCallback->PlayNotification(_id, callbackNotifyMs); } if(playEnded) { _ptrCallback->PlayFileEnded(_id); } } } WebRtc_Word32 MediaFileImpl::PlayoutStereoData( WebRtc_Word8* bufferLeft, WebRtc_Word8* bufferRight, WebRtc_UWord32& dataLengthInBytes) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "MediaFileImpl::PlayoutStereoData(Left = 0x%x, Right = 0x%x,\ Len= %ld)", bufferLeft, bufferRight, dataLengthInBytes); const WebRtc_UWord32 bufferLengthInBytes = dataLengthInBytes; dataLengthInBytes = 0; if(bufferLeft == NULL || bufferRight == NULL || bufferLengthInBytes == 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "A buffer pointer or the length is NULL!"); return -1; } bool playEnded = false; WebRtc_UWord32 callbackNotifyMs = 0; { CriticalSectionScoped lock(_crit); if(!_playingActive || !_isStereo) { WEBRTC_TRACE(kTraceWarning, kTraceFile, _id, "Not currently playing stereo!"); return -1; } if(!_ptrFileUtilityObj) { WEBRTC_TRACE( kTraceError, kTraceFile, _id, "Playing stereo, but the FileUtility objects is NULL!"); StopPlaying(); return -1; } // Stereo playout only supported for WAV files. WebRtc_Word32 bytesRead = 0; switch(_fileFormat) { case kFileFormatWavFile: bytesRead = _ptrFileUtilityObj->ReadWavDataAsStereo( *_ptrInStream, bufferLeft, bufferRight, bufferLengthInBytes); break; default: WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Trying to read non-WAV as stereo audio\ (not supported)"); break; } if(bytesRead > 0) { dataLengthInBytes = 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; } } CriticalSectionScoped lock(_callbackCrit); if(_ptrCallback) { if(callbackNotifyMs) { _ptrCallback->PlayNotification(_id, callbackNotifyMs); } if(playEnded) { _ptrCallback->PlayFileEnded(_id); } } return 0; } WebRtc_Word32 MediaFileImpl::StartPlayingAudioFile( const WebRtc_Word8* fileName, const WebRtc_UWord32 notificationTimeMs, const bool loop, const FileFormats format, const CodecInst* codecInst, const WebRtc_UWord32 startPointMs, const WebRtc_UWord32 stopPointMs) { const bool videoOnly = false; return StartPlayingFile(fileName, notificationTimeMs, loop, videoOnly, format, codecInst, startPointMs, stopPointMs); } WebRtc_Word32 MediaFileImpl::StartPlayingVideoFile(const WebRtc_Word8* fileName, const bool loop, bool videoOnly, const FileFormats format) { const WebRtc_UWord32 notificationTimeMs = 0; const WebRtc_UWord32 startPointMs = 0; const WebRtc_UWord32 stopPointMs = 0; return StartPlayingFile(fileName, notificationTimeMs, loop, videoOnly, format, 0, startPointMs, stopPointMs); } WebRtc_Word32 MediaFileImpl::StartPlayingFile( const WebRtc_Word8* fileName, const WebRtc_UWord32 notificationTimeMs, const bool loop, bool videoOnly, const FileFormats format, const CodecInst* codecInst, const WebRtc_UWord32 startPointMs, const WebRtc_UWord32 stopPointMs) { WEBRTC_TRACE( kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::StartPlayingFile: fileName= %s, notify= %d, loop= %d,\ format= %d, codecInst=%s, start= %d, stop= %d", (fileName == NULL) ? "NULL" : fileName, notificationTimeMs, loop, format,(codecInst == NULL) ? "NULL" : codecInst->plname, startPointMs, 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))) { WEBRTC_TRACE( kTraceError, kTraceFile, _id, "specified notification time is longer than amount of ms that will\ be played"); return -1; } FileWrapper* inputStream = FileWrapper::Create(); if(inputStream == NULL) { WEBRTC_TRACE(kTraceMemory, kTraceFile, _id, "Failed to allocate input stream for file %s", fileName); return -1; } // TODO (hellner): make all formats support reading from stream. bool useStream = (format != kFileFormatAviFile); if( useStream) { if(inputStream->OpenFile(fileName, true, loop) != 0) { delete inputStream; WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Could not open input file %s", fileName); return -1; } } if(StartPlayingStream(*inputStream, fileName, loop, notificationTimeMs, format, codecInst, startPointMs, stopPointMs, videoOnly) == -1) { if( useStream) { inputStream->CloseFile(); } delete inputStream; return -1; } CriticalSectionScoped lock(_crit); _openFile = true; strncpy(_fileName, fileName, sizeof(_fileName)); _fileName[sizeof(_fileName) - 1] = '\0'; return 0; } WebRtc_Word32 MediaFileImpl::StartPlayingAudioStream( InStream& stream, const WebRtc_UWord32 notificationTimeMs, const FileFormats format, const CodecInst* codecInst, const WebRtc_UWord32 startPointMs, const WebRtc_UWord32 stopPointMs) { return StartPlayingStream(stream, 0, false, notificationTimeMs, format, codecInst, startPointMs, stopPointMs); } WebRtc_Word32 MediaFileImpl::StartPlayingStream( InStream& stream, const WebRtc_Word8* filename, bool loop, const WebRtc_UWord32 notificationTimeMs, const FileFormats format, const CodecInst* codecInst, const WebRtc_UWord32 startPointMs, const WebRtc_UWord32 stopPointMs, bool videoOnly) { WEBRTC_TRACE( kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::StartPlayingStream(stream=0x%x, notify=%ld, format=%d,\ codecInst=%s, start=%ld, stop=%ld", &stream, notificationTimeMs, format, (codecInst == NULL) ? "NULL" : codecInst->plname, startPointMs, stopPointMs); if(!ValidFileFormat(format,codecInst)) { return -1; } if(!ValidFilePositions(startPointMs,stopPointMs)) { return -1; } CriticalSectionScoped lock(_crit); if(_playingActive || _recordingActive) { WEBRTC_TRACE( kTraceError, kTraceFile, _id, "StartPlaying called, but already playing or recording file %s", (_fileName == NULL) ? "NULL" : _fileName); return -1; } if(_ptrFileUtilityObj != NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "StartPlaying called, but FileUtilityObj already exists!"); StopPlaying(); return -1; } _ptrFileUtilityObj = new ModuleFileUtility(_id); if(_ptrFileUtilityObj == NULL) { WEBRTC_TRACE(kTraceMemory, kTraceFile, _id, "Failed to create FileUtilityObj!"); return -1; } switch(format) { case kFileFormatWavFile: { if(_ptrFileUtilityObj->InitWavReading(stream, startPointMs, stopPointMs) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Not a valid WAV file!"); StopPlaying(); return -1; } _fileFormat = kFileFormatWavFile; break; } case kFileFormatCompressedFile: { if(_ptrFileUtilityObj->InitCompressedReading(stream, startPointMs, stopPointMs) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Not a valid Compressed file!"); StopPlaying(); return -1; } _fileFormat = kFileFormatCompressedFile; break; } case kFileFormatPcm8kHzFile: case kFileFormatPcm16kHzFile: case kFileFormatPcm32kHzFile: { if(!ValidFrequency(codecInst->plfreq) || _ptrFileUtilityObj->InitPCMReading(stream, startPointMs, stopPointMs, codecInst->plfreq) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Not a valid raw 8 or 16 KHz PCM file!"); StopPlaying(); return -1; } _fileFormat = format; break; } case kFileFormatPreencodedFile: { if(_ptrFileUtilityObj->InitPreEncodedReading(stream, *codecInst) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Not a valid PreEncoded file!"); StopPlaying(); return -1; } _fileFormat = kFileFormatPreencodedFile; break; } #ifdef WEBRTC_MODULE_UTILITY_VIDEO case kFileFormatAviFile: { if(_ptrFileUtilityObj->InitAviReading( filename, videoOnly, loop)) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Not a valid AVI file!"); StopPlaying(); return -1; } _ptrFileUtilityObj->codec_info(codec_info_); _fileFormat = kFileFormatAviFile; break; } #endif default: { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Invalid file format specified!"); StopPlaying(); return -1; } } if(_ptrFileUtilityObj->codec_info(codec_info_) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Failed to retrieve codec info!"); StopPlaying(); return -1; } _isStereo = (codec_info_.channels == 2); if(_isStereo && (_fileFormat != kFileFormatWavFile)) { WEBRTC_TRACE(kTraceWarning, kTraceFile, _id, "Stereo is only allowed for WAV files"); StopPlaying(); return -1; } _playingActive = true; _playoutPositionMs = _ptrFileUtilityObj->PlayoutPositionMs(); _ptrInStream = &stream; _notificationMs = notificationTimeMs; return 0; } WebRtc_Word32 MediaFileImpl::StopPlaying() { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::StopPlaying()"); CriticalSectionScoped 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) { WEBRTC_TRACE(kTraceWarning, kTraceFile, _id, "playing is not active!"); return -1; } _playingActive = false; return 0; } bool MediaFileImpl::IsPlaying() { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "MediaFileImpl::IsPlaying()"); CriticalSectionScoped lock(_crit); return _playingActive; } WebRtc_Word32 MediaFileImpl::IncomingAudioData( const WebRtc_Word8* buffer, const WebRtc_UWord32 bufferLengthInBytes) { return IncomingAudioVideoData( buffer, bufferLengthInBytes, false); } WebRtc_Word32 MediaFileImpl::IncomingAVIVideoData( const WebRtc_Word8* buffer, const WebRtc_UWord32 bufferLengthInBytes) { return IncomingAudioVideoData( buffer, bufferLengthInBytes, true); } WebRtc_Word32 MediaFileImpl::IncomingAudioVideoData( const WebRtc_Word8* buffer, const WebRtc_UWord32 bufferLengthInBytes, const bool video) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "MediaFile::IncomingData(buffer= 0x%x, bufLen= %hd", buffer, bufferLengthInBytes); if(buffer == NULL || bufferLengthInBytes == 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Buffer pointer or length is NULL!"); return -1; } bool recordingEnded = false; WebRtc_UWord32 callbackNotifyMs = 0; { CriticalSectionScoped lock(_crit); if(!_recordingActive) { WEBRTC_TRACE(kTraceWarning, kTraceFile, _id, "Not currently recording!"); return -1; } if(_ptrOutStream == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Recording is active, but output stream is NULL!"); assert(false); return -1; } WebRtc_Word32 bytesWritten = 0; WebRtc_UWord32 samplesWritten = codec_info_.pacsize; if(_ptrFileUtilityObj) { switch(_fileFormat) { case kFileFormatPcm8kHzFile: case kFileFormatPcm16kHzFile: case kFileFormatPcm32kHzFile: bytesWritten = _ptrFileUtilityObj->WritePCMData( *_ptrOutStream, buffer, bufferLengthInBytes); // Sample size is 2 bytes. if(bytesWritten > 0) { samplesWritten = bytesWritten/sizeof(WebRtc_Word16); } 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(WebRtc_Word16); } break; case kFileFormatPreencodedFile: bytesWritten = _ptrFileUtilityObj->WritePreEncodedData( *_ptrOutStream, buffer, bufferLengthInBytes); break; #ifdef WEBRTC_MODULE_UTILITY_VIDEO case kFileFormatAviFile: if(video) { bytesWritten = _ptrFileUtilityObj->WriteAviVideoData( buffer, bufferLengthInBytes); }else { bytesWritten = _ptrFileUtilityObj->WriteAviAudioData( buffer, bufferLengthInBytes); } break; #endif default: WEBRTC_TRACE(kTraceError, kTraceFile, _id, "recording active, but file format invalid!"); 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 = bufferLengthInBytes; } } } if(!video) { _recordDurationMs += samplesWritten / (codec_info_.plfreq / 1000); } // Check if it's time for RecordNotification(..). if(_notificationMs) { if(_recordDurationMs >= _notificationMs) { _notificationMs = 0; callbackNotifyMs = _recordDurationMs; } } if(bytesWritten < (WebRtc_Word32)bufferLengthInBytes) { WEBRTC_TRACE(kTraceWarning, kTraceFile, _id, "Failed to write all requested bytes!"); StopRecording(); recordingEnded = true; } } // Only _callbackCrit may and should be taken when making callbacks. CriticalSectionScoped lock(_callbackCrit); if(_ptrCallback) { if(callbackNotifyMs) { _ptrCallback->RecordNotification(_id, callbackNotifyMs); } if(recordingEnded) { _ptrCallback->RecordFileEnded(_id); return -1; } } return 0; } WebRtc_Word32 MediaFileImpl::StartRecordingAudioFile( const WebRtc_Word8* fileName, const FileFormats format, const CodecInst& codecInst, const WebRtc_UWord32 notificationTimeMs, const WebRtc_UWord32 maxSizeBytes) { VideoCodec dummyCodecInst; return StartRecordingFile(fileName, format, codecInst, dummyCodecInst, notificationTimeMs, maxSizeBytes); } WebRtc_Word32 MediaFileImpl::StartRecordingVideoFile( const WebRtc_Word8* fileName, const FileFormats format, const CodecInst& codecInst, const VideoCodec& videoCodecInst, bool videoOnly) { const WebRtc_UWord32 notificationTimeMs = 0; const WebRtc_UWord32 maxSizeBytes = 0; return StartRecordingFile(fileName, format, codecInst, videoCodecInst, notificationTimeMs, maxSizeBytes, videoOnly); } WebRtc_Word32 MediaFileImpl::StartRecordingFile( const WebRtc_Word8* fileName, const FileFormats format, const CodecInst& codecInst, const VideoCodec& videoCodecInst, const WebRtc_UWord32 notificationTimeMs, const WebRtc_UWord32 maxSizeBytes, bool videoOnly) { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::StartRecordingFile(fileName= %s, format= %d,\ codecInst= %s, notificationMs= %d, maxSize= %d", (fileName == NULL) ? "NULL" : fileName, format, (codecInst.plname[0] == '\0') ? "NULL" : codecInst.plname, notificationTimeMs, maxSizeBytes); if(!ValidFileName(fileName)) { return -1; } if(!ValidFileFormat(format,&codecInst)) { return -1; } FileWrapper* outputStream = FileWrapper::Create(); if(outputStream == NULL) { WEBRTC_TRACE(kTraceMemory, kTraceFile, _id, "Failed to allocate memory for output stream"); return -1; } // TODO (hellner): make all formats support writing to stream. const bool useStream = ( format != kFileFormatAviFile); if( useStream) { if(outputStream->OpenFile(fileName, false) != 0) { delete outputStream; WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Could not open output file '%s' for writing!", fileName); return -1; } } if(maxSizeBytes) { outputStream->SetMaxFileSize(maxSizeBytes); } if(StartRecordingStream(*outputStream, fileName, format, codecInst, videoCodecInst, notificationTimeMs, videoOnly) == -1) { if( useStream) { outputStream->CloseFile(); } delete outputStream; return -1; } CriticalSectionScoped lock(_crit); _openFile = true; strncpy(_fileName, fileName, sizeof(_fileName)); _fileName[sizeof(_fileName) - 1] = '\0'; return 0; } WebRtc_Word32 MediaFileImpl::StartRecordingAudioStream( OutStream& stream, const FileFormats format, const CodecInst& codecInst, const WebRtc_UWord32 notificationTimeMs) { VideoCodec dummyCodecInst; return StartRecordingStream(stream, 0, format, codecInst, dummyCodecInst, notificationTimeMs); } WebRtc_Word32 MediaFileImpl::StartRecordingStream( OutStream& stream, const WebRtc_Word8* fileName, const FileFormats format, const CodecInst& codecInst, const VideoCodec& videoCodecInst, const WebRtc_UWord32 notificationTimeMs, bool videoOnly) { WEBRTC_TRACE( kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::StartRecordingStream: format= %d, codec= %s,\ notify= %d", format,(codecInst.plname[0] == '\0') ? "NULL" : codecInst.plname, notificationTimeMs); // Check codec info if(!ValidFileFormat(format,&codecInst)) { return -1; } CriticalSectionScoped lock(_crit); if(_recordingActive || _playingActive) { WEBRTC_TRACE( kTraceError, kTraceFile, _id, "StartRecording called, but already recording or playing file %s!", _fileName); return -1; } if(_ptrFileUtilityObj != NULL) { WEBRTC_TRACE( kTraceError, kTraceFile, _id, "StartRecording called, but fileUtilityObj already exists!"); StopRecording(); return -1; } _ptrFileUtilityObj = new ModuleFileUtility(_id); if(_ptrFileUtilityObj == NULL) { WEBRTC_TRACE(kTraceMemory, kTraceFile, _id, "Cannot allocate fileUtilityObj!"); return -1; } CodecInst tmpAudioCodec; memcpy(&tmpAudioCodec, &codecInst, sizeof(CodecInst)); switch(format) { case kFileFormatWavFile: { if(_ptrFileUtilityObj->InitWavWriting(stream, codecInst) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "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) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Failed to initialize Compressed file!"); delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } _fileFormat = kFileFormatCompressedFile; break; } case kFileFormatPcm8kHzFile: case kFileFormatPcm16kHzFile: { if(!ValidFrequency(codecInst.plfreq) || _ptrFileUtilityObj->InitPCMWriting(stream, codecInst.plfreq) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Failed to initialize 8 or 16KHz PCM file!"); delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } _fileFormat = format; break; } case kFileFormatPreencodedFile: { if(_ptrFileUtilityObj->InitPreEncodedWriting(stream, codecInst) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Failed to initialize Pre-Encoded file!"); delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } _fileFormat = kFileFormatPreencodedFile; break; } #ifdef WEBRTC_MODULE_UTILITY_VIDEO case kFileFormatAviFile: { if( (_ptrFileUtilityObj->InitAviWriting( fileName, codecInst, videoCodecInst,videoOnly) == -1) || (_ptrFileUtilityObj->codec_info(tmpAudioCodec) != 0)) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Failed to initialize AVI file!"); delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } _fileFormat = kFileFormatAviFile; break; } #endif default: { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Invalid file format %d specified!", format); delete _ptrFileUtilityObj; _ptrFileUtilityObj = NULL; return -1; } } _isStereo = (tmpAudioCodec.channels == 2); if(_isStereo) { if(_fileFormat != kFileFormatWavFile) { WEBRTC_TRACE(kTraceWarning, kTraceFile, _id, "Stereo is only allowed for WAV files"); StopRecording(); return -1; } if((STR_NCASE_CMP(codec_info_.plname, "L16", 4) != 0) && (STR_NCASE_CMP(codec_info_.plname, "PCMU", 5) != 0) && (STR_NCASE_CMP(codec_info_.plname, "PCMA", 5) != 0)) { WEBRTC_TRACE( kTraceWarning, kTraceFile, _id, "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; } WebRtc_Word32 MediaFileImpl::StopRecording() { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::StopRecording()"); CriticalSectionScoped lock(_crit); if(!_recordingActive) { WEBRTC_TRACE(kTraceWarning, kTraceFile, _id, "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); } #ifdef WEBRTC_MODULE_UTILITY_VIDEO else if( _fileFormat == kFileFormatAviFile) { _ptrFileUtilityObj->CloseAviFile( ); } #endif 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() { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "MediaFileImpl::IsRecording()"); CriticalSectionScoped lock(_crit); return _recordingActive; } WebRtc_Word32 MediaFileImpl::RecordDurationMs(WebRtc_UWord32& durationMs) { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::RecordDurationMs()"); CriticalSectionScoped lock(_crit); if(!_recordingActive) { durationMs = 0; return -1; } durationMs = _recordDurationMs; return 0; } bool MediaFileImpl::IsStereo() { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "MediaFileImpl::IsStereo()"); CriticalSectionScoped lock(_crit); return _isStereo; } WebRtc_Word32 MediaFileImpl::SetModuleFileCallback(FileCallback* callback) { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::SetModuleFileCallback(callback= 0x%x)", &callback); CriticalSectionScoped lock(_callbackCrit); _ptrCallback = callback; return 0; } WebRtc_Word32 MediaFileImpl::FileDurationMs(const WebRtc_Word8* fileName, WebRtc_UWord32& durationMs, const FileFormats format, const WebRtc_UWord32 freqInHz) { WEBRTC_TRACE( kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::FileDurationMs(%s, -, format= %d, freqInHz= %d)", (fileName == NULL) ? "NULL" : fileName, format, freqInHz); if(!ValidFileName(fileName)) { return -1; } if(!ValidFrequency(freqInHz)) { return -1; } ModuleFileUtility* utilityObj = new ModuleFileUtility(_id); if(utilityObj == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "failed to allocate utility object!"); return -1; } const WebRtc_Word32 duration = utilityObj->FileDurationMs(fileName, format, freqInHz); delete utilityObj; if(duration == -1) { durationMs = 0; return -1; } durationMs = duration; return 0; } WebRtc_Word32 MediaFileImpl::PlayoutPositionMs(WebRtc_UWord32& positionMs) const { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::PlayoutPositionMS(?)"); CriticalSectionScoped lock(_crit); if(!_playingActive) { positionMs = 0; return -1; } positionMs = _playoutPositionMs; return 0; } WebRtc_Word32 MediaFileImpl::codec_info(CodecInst& codecInst) const { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::codec_info(CodecInst= 0x%x)", &codecInst); CriticalSectionScoped lock(_crit); if(!_playingActive && !_recordingActive) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Neither playout nor recording has been initialized!"); return -1; } if (codec_info_.pltype == 0 && codec_info_.plname[0] == '\0') { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "The CodecInst for %s is unknown!", _playingActive ? "Playback" : "Recording"); return -1; } memcpy(&codecInst,&codec_info_,sizeof(CodecInst)); return 0; } WebRtc_Word32 MediaFileImpl::VideoCodecInst(VideoCodec& codecInst) const { WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "MediaFileImpl::VideoCodecInst(CodecInst= 0x%x)", &codecInst); CriticalSectionScoped lock(_crit); if(!_playingActive && !_recordingActive) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Neither playout nor recording has been initialized!"); return -1; } if( _ptrFileUtilityObj == NULL) { return -1; } #ifdef WEBRTC_MODULE_UTILITY_VIDEO VideoCodec videoCodec; if( _ptrFileUtilityObj->VideoCodecInst( videoCodec) != 0) { return -1; } memcpy(&codecInst,&videoCodec,sizeof(VideoCodec)); return 0; #else return -1; #endif } bool MediaFileImpl::ValidFileFormat(const FileFormats format, const CodecInst* codecInst) { if(codecInst == NULL) { if(format == kFileFormatPreencodedFile || format == kFileFormatPcm8kHzFile || format == kFileFormatPcm16kHzFile || format == kFileFormatPcm32kHzFile) { WEBRTC_TRACE(kTraceError, kTraceFile, -1, "Codec info required for file format specified!"); return false; } } return true; } bool MediaFileImpl::ValidFileName(const WebRtc_Word8* fileName) { if((fileName == NULL) ||(fileName[0] == '\0')) { WEBRTC_TRACE(kTraceError, kTraceFile, -1, "FileName not specified!"); return false; } return true; } bool MediaFileImpl::ValidFilePositions(const WebRtc_UWord32 startPointMs, const WebRtc_UWord32 stopPointMs) { if(startPointMs == 0 && stopPointMs == 0) // Default values { return true; } if(stopPointMs &&(startPointMs >= stopPointMs)) { WEBRTC_TRACE(kTraceError, kTraceFile, -1, "startPointMs must be less than stopPointMs!"); return false; } if(stopPointMs &&((stopPointMs - startPointMs) < 20)) { WEBRTC_TRACE(kTraceError, kTraceFile, -1, "minimum play duration for files is 20 ms!"); return false; } return true; } bool MediaFileImpl::ValidFrequency(const WebRtc_UWord32 frequency) { if((frequency == 8000) || (frequency == 16000)|| (frequency == 32000)) { return true; } WEBRTC_TRACE(kTraceError, kTraceFile, -1, "Frequency should be 8000, 16000 or 32000 (Hz)"); return false; } } // namespace webrtc