blob: 11f70f6e207c45a054e4a65f7e92b1c1fb9859c6 [file] [log] [blame]
/*
* 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/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/engine_configurations.h"
#include "webrtc/modules/media_file/interface/media_file.h"
#include "webrtc/modules/utility/source/file_recorder_impl.h"
#include "webrtc/system_wrappers/interface/logging.h"
#ifdef WEBRTC_MODULE_UTILITY_VIDEO
#include "webrtc/modules/utility/source/frame_scaler.h"
#include "webrtc/modules/utility/source/video_coder.h"
#include "webrtc/modules/utility/source/video_frames_queue.h"
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
#endif
namespace webrtc {
FileRecorder* FileRecorder::CreateFileRecorder(uint32_t instanceID,
FileFormats fileFormat)
{
switch(fileFormat)
{
case kFileFormatWavFile:
case kFileFormatCompressedFile:
case kFileFormatPreencodedFile:
case kFileFormatPcm16kHzFile:
case kFileFormatPcm8kHzFile:
case kFileFormatPcm32kHzFile:
return new FileRecorderImpl(instanceID, fileFormat);
case kFileFormatAviFile:
#ifdef WEBRTC_MODULE_UTILITY_VIDEO
return new AviRecorder(instanceID, fileFormat);
#else
assert(false);
return NULL;
#endif
}
assert(false);
return NULL;
}
void FileRecorder::DestroyFileRecorder(FileRecorder* recorder)
{
delete recorder;
}
FileRecorderImpl::FileRecorderImpl(uint32_t instanceID,
FileFormats fileFormat)
: _instanceID(instanceID),
_fileFormat(fileFormat),
_moduleFile(MediaFile::CreateMediaFile(_instanceID)),
codec_info_(),
_amrFormat(AMRFileStorage),
_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,
ACMAMRPackingFormat amrFormat)
{
if(_moduleFile == NULL)
{
return -1;
}
codec_info_ = codecInst;
_amrFormat = amrFormat;
int32_t retVal = 0;
if(_fileFormat != kFileFormatAviFile)
{
// AVI files should be started using StartRecordingVideoFile(..) all
// other formats should use this API.
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,
ACMAMRPackingFormat amrFormat)
{
codec_info_ = codecInst;
_amrFormat = amrFormat;
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,
const TickTime* playoutTS)
{
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 (uint16_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 (uint16_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 {
int outLen = 0;
if(ptrAudioFrame->num_channels_ == 2)
{
// ptrAudioFrame contains interleaved stereo audio.
_audioResampler.ResetIfNeeded(ptrAudioFrame->sample_rate_hz_,
codec_info_.plfreq,
kResamplerSynchronousStereo);
_audioResampler.Push(ptrAudioFrame->data_,
ptrAudioFrame->samples_per_channel_ *
ptrAudioFrame->num_channels_,
(int16_t*)_audioBuffer,
MAX_AUDIO_BUFFER_IN_BYTES, outLen);
} else {
_audioResampler.ResetIfNeeded(ptrAudioFrame->sample_rate_hz_,
codec_info_.plfreq,
kResamplerSynchronous);
_audioResampler.Push(ptrAudioFrame->data_,
ptrAudioFrame->samples_per_channel_,
(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)
{
uint16_t msOfData =
ptrAudioFrame->samples_per_channel_ /
uint16_t(ptrAudioFrame->sample_rate_hz_ / 1000);
if (WriteEncodedAudioData(_audioBuffer, encodedLenInBytes, msOfData,
playoutTS) == -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_,_amrFormat) == -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,
uint16_t /*millisecondsOfData*/,
const TickTime* /*playoutTS*/)
{
return _moduleFile->IncomingAudioData(audioBuffer, bufferLength);
}
#ifdef WEBRTC_MODULE_UTILITY_VIDEO
AviRecorder::AviRecorder(uint32_t instanceID, FileFormats fileFormat)
: FileRecorderImpl(instanceID, fileFormat),
_videoOnly(false),
_thread( 0),
_timeEvent(*EventWrapper::Create()),
_critSec(CriticalSectionWrapper::CreateCriticalSection()),
_writtenVideoFramesCounter(0),
_writtenAudioMS(0),
_writtenVideoMS(0)
{
_videoEncoder = new VideoCoder();
_frameScaler = new FrameScaler();
_videoFramesQueue = new VideoFramesQueue();
_thread = ThreadWrapper::CreateThread(Run, this, kNormalPriority,
"AviRecorder()");
}
AviRecorder::~AviRecorder( )
{
StopRecording( );
delete _videoEncoder;
delete _frameScaler;
delete _videoFramesQueue;
delete _thread;
delete &_timeEvent;
delete _critSec;
}
int32_t AviRecorder::StartRecordingVideoFile(
const char* fileName,
const CodecInst& audioCodecInst,
const VideoCodec& videoCodecInst,
ACMAMRPackingFormat amrFormat,
bool videoOnly)
{
_firstAudioFrameReceived = false;
_videoCodecInst = videoCodecInst;
_videoOnly = videoOnly;
if(_moduleFile->StartRecordingVideoFile(fileName, _fileFormat,
audioCodecInst, videoCodecInst,
videoOnly) != 0)
{
return -1;
}
if(!videoOnly)
{
if(FileRecorderImpl::StartRecordingAudioFile(fileName,audioCodecInst, 0,
amrFormat) !=0)
{
StopRecording();
return -1;
}
}
if( SetUpVideoEncoder() != 0)
{
StopRecording();
return -1;
}
if(_videoOnly)
{
// Writing to AVI file is non-blocking.
// Start non-blocking timer if video only. If recording both video and
// audio let the pushing of audio frames be the timer.
_timeEvent.StartTimer(true, 1000 / _videoCodecInst.maxFramerate);
}
StartThread();
return 0;
}
int32_t AviRecorder::StopRecording()
{
_timeEvent.StopTimer();
StopThread();
return FileRecorderImpl::StopRecording();
}
size_t AviRecorder::CalcI420FrameSize( ) const
{
return 3 * _videoCodecInst.width * _videoCodecInst.height / 2;
}
int32_t AviRecorder::SetUpVideoEncoder()
{
// Size of unencoded data (I420) should be the largest possible frame size
// in a file.
_videoMaxPayloadSize = CalcI420FrameSize();
_videoEncodedData.VerifyAndAllocate(_videoMaxPayloadSize);
_videoCodecInst.plType = _videoEncoder->DefaultPayloadType(
_videoCodecInst.plName);
int32_t useNumberOfCores = 1;
// Set the max payload size to 16000. This means that the codec will try to
// create slices that will fit in 16000 kByte packets. However, the
// Encode() call will still generate one full frame.
if(_videoEncoder->SetEncodeCodec(_videoCodecInst, useNumberOfCores,
16000))
{
return -1;
}
return 0;
}
int32_t AviRecorder::RecordVideoToFile(const I420VideoFrame& videoFrame)
{
CriticalSectionScoped lock(_critSec);
if(!IsRecording() || videoFrame.IsZeroSize())
{
return -1;
}
// The frame is written to file in AviRecorder::Process().
int32_t retVal = _videoFramesQueue->AddFrame(videoFrame);
if(retVal != 0)
{
StopRecording();
}
return retVal;
}
bool AviRecorder::StartThread()
{
unsigned int id;
if( _thread == 0)
{
return false;
}
return _thread->Start(id);
}
bool AviRecorder::StopThread()
{
_critSec->Enter();
if(_thread)
{
ThreadWrapper* thread = _thread;
_thread = NULL;
_timeEvent.Set();
_critSec->Leave();
if(thread->Stop())
{
delete thread;
} else {
return false;
}
} else {
_critSec->Leave();
}
return true;
}
bool AviRecorder::Run( ThreadObj threadObj)
{
return static_cast<AviRecorder*>( threadObj)->Process();
}
int32_t AviRecorder::ProcessAudio()
{
if (_writtenVideoFramesCounter == 0)
{
// Get the most recent frame that is due for writing to file. Since
// frames are unencoded it's safe to throw away frames if necessary
// for synchronizing audio and video.
I420VideoFrame* frameToProcess = _videoFramesQueue->FrameToRecord();
if(frameToProcess)
{
// Syncronize audio to the current frame to process by throwing away
// audio samples with older timestamp than the video frame.
size_t numberOfAudioElements =
_audioFramesToWrite.size();
for (size_t i = 0; i < numberOfAudioElements; ++i)
{
AudioFrameFileInfo* frameInfo = _audioFramesToWrite.front();
if(TickTime::TicksToMilliseconds(
frameInfo->_playoutTS.Ticks()) <
frameToProcess->render_time_ms())
{
delete frameInfo;
_audioFramesToWrite.pop_front();
} else
{
break;
}
}
}
}
// Write all audio up to current timestamp.
int32_t error = 0;
size_t numberOfAudioElements = _audioFramesToWrite.size();
for (size_t i = 0; i < numberOfAudioElements; ++i)
{
AudioFrameFileInfo* frameInfo = _audioFramesToWrite.front();
if((TickTime::Now() - frameInfo->_playoutTS).Milliseconds() > 0)
{
_moduleFile->IncomingAudioData(frameInfo->_audioData,
frameInfo->_audioSize);
_writtenAudioMS += frameInfo->_audioMS;
delete frameInfo;
_audioFramesToWrite.pop_front();
} else {
break;
}
}
return error;
}
bool AviRecorder::Process()
{
switch(_timeEvent.Wait(500))
{
case kEventSignaled:
if(_thread == NULL)
{
return false;
}
break;
case kEventError:
return false;
case kEventTimeout:
// No events triggered. No work to do.
return true;
}
CriticalSectionScoped lock( _critSec);
// Get the most recent frame to write to file (if any). Synchronize it with
// the audio stream (if any). Synchronization the video based on its render
// timestamp (i.e. VideoFrame::RenderTimeMS())
I420VideoFrame* frameToProcess = _videoFramesQueue->FrameToRecord();
if( frameToProcess == NULL)
{
return true;
}
int32_t error = 0;
if(!_videoOnly)
{
if(!_firstAudioFrameReceived)
{
// Video and audio can only be synchronized if both have been
// received.
return true;
}
error = ProcessAudio();
while (_writtenAudioMS > _writtenVideoMS)
{
error = EncodeAndWriteVideoToFile( *frameToProcess);
if( error != 0)
{
LOG(LS_ERROR) << "AviRecorder::Process() error writing to "
<< "file.";
break;
} else {
uint32_t frameLengthMS = 1000 /
_videoCodecInst.maxFramerate;
_writtenVideoFramesCounter++;
_writtenVideoMS += frameLengthMS;
// A full seconds worth of frames have been written.
if(_writtenVideoFramesCounter%_videoCodecInst.maxFramerate == 0)
{
// Frame rate is in frames per seconds. Frame length is
// calculated as an integer division which means it may
// be rounded down. Compensate for this every second.
uint32_t rest = 1000 % frameLengthMS;
_writtenVideoMS += rest;
}
}
}
} else {
// Frame rate is in frames per seconds. Frame length is calculated as an
// integer division which means it may be rounded down. This introduces
// drift. Once a full frame worth of drift has happened, skip writing
// one frame. Note that frame rate is in frames per second so the
// drift is completely compensated for.
uint32_t frameLengthMS = 1000/_videoCodecInst.maxFramerate;
uint32_t restMS = 1000 % frameLengthMS;
uint32_t frameSkip = (_videoCodecInst.maxFramerate *
frameLengthMS) / restMS;
_writtenVideoFramesCounter++;
if(_writtenVideoFramesCounter % frameSkip == 0)
{
_writtenVideoMS += frameLengthMS;
return true;
}
error = EncodeAndWriteVideoToFile( *frameToProcess);
if(error != 0)
{
LOG(LS_ERROR) << "AviRecorder::Process() error writing to file.";
} else {
_writtenVideoMS += frameLengthMS;
}
}
return error == 0;
}
int32_t AviRecorder::EncodeAndWriteVideoToFile(I420VideoFrame& videoFrame)
{
if (!IsRecording() || videoFrame.IsZeroSize())
{
return -1;
}
if(_frameScaler->ResizeFrameIfNeeded(&videoFrame, _videoCodecInst.width,
_videoCodecInst.height) != 0)
{
return -1;
}
_videoEncodedData.payloadSize = 0;
if( STR_CASE_CMP(_videoCodecInst.plName, "I420") == 0)
{
size_t length =
CalcBufferSize(kI420, videoFrame.width(), videoFrame.height());
_videoEncodedData.VerifyAndAllocate(length);
// I420 is raw data. No encoding needed (each sample is represented by
// 1 byte so there is no difference depending on endianness).
int ret_length = ExtractBuffer(videoFrame, length,
_videoEncodedData.payloadData);
if (ret_length < 0)
return -1;
_videoEncodedData.payloadSize = ret_length;
_videoEncodedData.frameType = kVideoFrameKey;
}else {
if( _videoEncoder->Encode(videoFrame, _videoEncodedData) != 0)
{
return -1;
}
}
if(_videoEncodedData.payloadSize > 0)
{
if(_moduleFile->IncomingAVIVideoData(
(int8_t*)(_videoEncodedData.payloadData),
_videoEncodedData.payloadSize))
{
LOG(LS_ERROR) << "Error writing AVI file.";
return -1;
}
} else {
LOG(LS_ERROR) << "FileRecorder::RecordVideoToFile() frame dropped by "
<< "encoder, bitrate likely too low.";
}
return 0;
}
// Store audio frame in the _audioFramesToWrite buffer. The writing to file
// happens in AviRecorder::Process().
int32_t AviRecorder::WriteEncodedAudioData(
const int8_t* audioBuffer,
size_t bufferLength,
uint16_t millisecondsOfData,
const TickTime* playoutTS)
{
CriticalSectionScoped lock(_critSec);
if (!IsRecording())
{
return -1;
}
if (bufferLength > MAX_AUDIO_BUFFER_IN_BYTES)
{
return -1;
}
if (_videoOnly)
{
return -1;
}
if (_audioFramesToWrite.size() > kMaxAudioBufferQueueLength)
{
StopRecording();
return -1;
}
_firstAudioFrameReceived = true;
if(playoutTS)
{
_audioFramesToWrite.push_back(new AudioFrameFileInfo(audioBuffer,
bufferLength,
millisecondsOfData,
*playoutTS));
} else {
_audioFramesToWrite.push_back(new AudioFrameFileInfo(audioBuffer,
bufferLength,
millisecondsOfData,
TickTime::Now()));
}
_timeEvent.Set();
return 0;
}
#endif // WEBRTC_MODULE_UTILITY_VIDEO
} // namespace webrtc