You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
811 lines
24 KiB
811 lines
24 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
//---------------------------------------------------------------------------------------- |
|
|
|
#define WIN32_LEAN_AND_MEAN |
|
|
|
#include "webm_video.h" |
|
#include "webm_recorder.h" |
|
#include "filesystem.h" |
|
|
|
#ifdef _WIN32 |
|
#include "windows.h" |
|
#endif |
|
|
|
|
|
// Bitrate table for various resolutions, used to compute estimated size of files as well |
|
// as to set the WebM codec. |
|
|
|
struct WebMEncodingDataRateInfo_t |
|
{ |
|
int m_XResolution; |
|
int m_YResolution; |
|
float m_MinDataRate; // in KBits / second |
|
float m_MaxDataRate; // in KBits / second |
|
float m_MinAudioDataRate; // in KBits / second |
|
float m_MaxAudioDataRate; // in KBits / second |
|
}; |
|
|
|
// Quality is passed into us as a number between 0 and 100, we use that to scale between the min and max bitrates. |
|
static WebMEncodingDataRateInfo_t s_WebMEncodeRates[] = |
|
{ |
|
{ 320, 240, 1000, 1000, 128, 128}, |
|
{ 640, 960, 2000, 2000, 128, 128}, |
|
{ 960, 640, 2000, 2000, 128, 128}, |
|
{ 720, 480, 2500, 2500, 128, 128}, |
|
{1280, 720, 5000, 5000, 384, 384}, |
|
{1920, 1080, 8000, 8000, 384, 384}, |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// =========================================================================== |
|
// CWebMVideoRecorder class - implements IVideoRecorder interface for |
|
// WebM and buffers commands to the actual encoder object |
|
// =========================================================================== |
|
CWebMVideoRecorder::CWebMVideoRecorder() : |
|
m_LastResult( VideoResult::SUCCESS ), |
|
m_bHasAudio( false ), |
|
m_bMovieFinished( false ), |
|
m_SrcImageYV12Buffer( NULL ), |
|
m_nFramesAdded( 0 ), |
|
m_nAudioFramesAdded( 0 ), |
|
m_nSamplesAdded( 0 ), |
|
m_audioChannels( 2 ), |
|
m_audioSampleRate( 44100 ), |
|
m_audioBitDepth( 16 ), |
|
m_audioSampleGroupSize( 0 ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::CWebMVideoRecorder() \n"); |
|
#endif |
|
} |
|
|
|
|
|
CWebMVideoRecorder::~CWebMVideoRecorder() |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::~CWebMVideoRecorder() \n"); |
|
#endif |
|
if (m_SrcImageYV12Buffer) |
|
{ |
|
vpx_img_free(m_SrcImageYV12Buffer); |
|
m_SrcImageYV12Buffer = NULL; |
|
} |
|
} |
|
|
|
|
|
// All webm movies use 1000000 as a scale |
|
#define WEBM_TIMECODE_SCALE 1000000 |
|
// All webm movies using 128kbps as audio bitrate |
|
#define WEBM_AUDIO_BITRATE 128000 |
|
|
|
|
|
bool CWebMVideoRecorder::CreateNewMovieFile( const char *pFilename, bool hasAudio ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::CreateNewMovieFile() \n"); |
|
#endif |
|
|
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
AssertExitF( IS_NOT_EMPTY( pFilename ) ); |
|
|
|
SetResult( VideoResult::OPERATION_ALREADY_PERFORMED ); |
|
|
|
// crete the webm file |
|
if (!m_mkvWriter.Open(pFilename)) |
|
{ |
|
SetResult( VideoResult::OPERATION_ALREADY_PERFORMED ); |
|
return false; |
|
} |
|
|
|
// init the webm file and write out the header |
|
m_mkvMuxerSegment.Init(&m_mkvWriter); |
|
m_mkvMuxerSegment.set_mode(mkvmuxer::Segment::kFile); |
|
m_mkvMuxerSegment.OutputCues(true); |
|
|
|
|
|
mkvmuxer::SegmentInfo* const mkvInfo = m_mkvMuxerSegment.GetSegmentInfo(); |
|
mkvInfo->set_timecode_scale(WEBM_TIMECODE_SCALE); |
|
|
|
// get the app name |
|
char appname[ 256 ]; |
|
KeyValues *modinfo = new KeyValues( "ModInfo" ); |
|
|
|
if ( modinfo->LoadFromFile( g_pFullFileSystem, "gameinfo.txt" ) ) |
|
Q_strncpy( appname, modinfo->GetString( "game" ), sizeof( appname ) ); |
|
else |
|
Q_strncpy( appname, "Source1 Game", sizeof( appname ) ); |
|
|
|
modinfo->deleteThis(); |
|
modinfo = NULL; |
|
|
|
mkvInfo->set_writing_app(appname); |
|
|
|
m_bHasAudio = hasAudio; |
|
|
|
SetResult( VideoResult::SUCCESS ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CWebMVideoRecorder::SetMovieVideoParameters( VideoEncodeCodec_t theCodec, int videoQuality, int movieFrameWidth, int movieFrameHeight, VideoFrameRate_t movieFPS, VideoEncodeGamma_t gamma ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::SetMovieVideoParameters()\n"); |
|
#endif |
|
|
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
AssertExitF( IS_IN_RANGECOUNT( theCodec, VideoEncodeCodec::DEFAULT_CODEC, VideoEncodeCodec::CODEC_COUNT ) ); |
|
AssertExitF( IS_IN_RANGE( videoQuality, VideoEncodeQuality::MIN_QUALITY, VideoEncodeQuality::MAX_QUALITY ) ); |
|
AssertExitF( IS_IN_RANGE( movieFrameWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth ) && IS_IN_RANGE( movieFrameHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight ) ); |
|
AssertExitF( IS_IN_RANGE( movieFPS.GetFPS(), cMinFPS, cMaxFPS ) ); |
|
AssertExitF( IS_IN_RANGECOUNT( gamma, VideoEncodeGamma::NO_GAMMA_ADJUST, VideoEncodeGamma::GAMMA_COUNT ) ); |
|
|
|
SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE ); |
|
|
|
// Get the defaults for the WebM encoder |
|
if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &m_vpxConfig, 0) != VPX_CODEC_OK) |
|
{ |
|
SetResult( VideoResult::INITIALIZATION_ERROR_OCCURED ); |
|
return false; |
|
} |
|
|
|
m_MovieFrameWidth = movieFrameWidth; |
|
m_MovieFrameHeight = movieFrameHeight; |
|
|
|
m_MovieGamma = gamma; |
|
|
|
m_vpxConfig.g_h = movieFrameHeight; |
|
m_vpxConfig.g_w = movieFrameWidth; |
|
|
|
// How many threads should we use? How about 1 per logical processor |
|
const CPUInformation *pi = GetCPUInformation(); |
|
m_vpxConfig.g_threads = pi->m_nLogicalProcessors; |
|
|
|
// FPS |
|
m_vpxConfig.g_timebase.den = movieFPS.GetUnitsPerSecond(); |
|
m_vpxConfig.g_timebase.num = movieFPS.GetUnitsPerFrame(); |
|
|
|
m_MovieRecordFPS = movieFPS; |
|
|
|
m_DurationPerFrame = m_MovieRecordFPS.GetUnitsPerFrame(); |
|
m_MovieTimeScale = m_MovieRecordFPS.GetUnitsPerSecond(); |
|
|
|
// Compute how long each frame is, in nanoseconds |
|
m_FrameDuration = (unsigned long)(((double)m_DurationPerFrame*(double)1000000000)/(double)m_MovieTimeScale); |
|
|
|
// Set the bitrate for this size and level of quality |
|
m_vpxConfig.rc_target_bitrate = GetVideoDataRate(videoQuality, movieFrameWidth, movieFrameHeight); |
|
|
|
|
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg( "Video Frame Rate = %f FPS\n %d time units per second\n %d time units per frame\n", m_MovieRecordFPS.GetFPS(), m_MovieRecordFPS.GetUnitsPerSecond(), m_MovieRecordFPS.GetUnitsPerFrame() ); |
|
if ( m_MovieRecordFPS.IsNTSCRate() ) |
|
Msg( " IS CONSIDERED NTSC RATE\n"); |
|
Msg( "Using %d threads for WebM encoding\n", m_vpxConfig.g_threads); |
|
Msg( "MovieTimeScale is being set to %d\nDuration Per Frame is %d\n", m_MovieTimeScale, m_DurationPerFrame ); |
|
Msg( "Time per frame in nanoseconds %d\n\n", m_FrameDuration); |
|
|
|
#endif |
|
|
|
// Init the codec |
|
if (vpx_codec_enc_init(&m_vpxContext, vpx_codec_vp8_cx(), &m_vpxConfig, 0) != VPX_CODEC_OK) |
|
{ |
|
SetResult( VideoResult::INITIALIZATION_ERROR_OCCURED ); |
|
return false; |
|
} |
|
|
|
// add the video track |
|
m_vid_track = m_mkvMuxerSegment.AddVideoTrack(static_cast<int>(m_vpxConfig.g_w), |
|
static_cast<int>(m_vpxConfig.g_h), |
|
1); |
|
|
|
mkvmuxer::VideoTrack* const video = |
|
static_cast<mkvmuxer::VideoTrack*>( |
|
m_mkvMuxerSegment.GetTrackByNumber(m_vid_track)); |
|
|
|
video->set_display_width(m_vpxConfig.g_w); |
|
video->set_display_height(m_vpxConfig.g_h); |
|
video->set_frame_rate(movieFPS.GetFPS()); |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CWebMVideoRecorder::SetMovieSourceImageParameters( VideoEncodeSourceFormat_t srcImageFormat, int imgWidth, int imgHeight ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::SetMovieSourceImageParameters()\n"); |
|
#endif |
|
|
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
AssertExitF( IS_IN_RANGECOUNT( srcImageFormat, VideoEncodeSourceFormat::VIDEO_FORMAT_FIRST, VideoEncodeSourceFormat::VIDEO_FORMAT_COUNT ) ); |
|
// WebM Recorder only supports BGRA_32BIT and BGR_24BIT |
|
AssertExitF( (srcImageFormat == VideoEncodeSourceFormat::BGRA_32BIT) || |
|
(srcImageFormat == VideoEncodeSourceFormat::BGR_24BIT) ); |
|
|
|
AssertExitF( IS_IN_RANGE( imgWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth ) && IS_IN_RANGE( imgHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight ) ); |
|
|
|
SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE ); |
|
|
|
m_SrcImageWidth = imgWidth; |
|
m_SrcImageHeight = imgHeight; |
|
m_SrcImageFormat = srcImageFormat; |
|
|
|
// Setup the buffers for encoding the frame. WebM requires a frame in YV12 format |
|
m_SrcImageYV12Buffer = vpx_img_alloc(NULL, VPX_IMG_FMT_YV12, m_SrcImageWidth, m_SrcImageHeight, 1); |
|
|
|
// Set Cues element attributes |
|
mkvmuxer::Cues* const cues = m_mkvMuxerSegment.GetCues(); |
|
cues->set_output_block_number(1); |
|
m_mkvMuxerSegment.CuesTrack(m_vid_track); |
|
|
|
SetResult( VideoResult::SUCCESS ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CWebMVideoRecorder::SetMovieSourceAudioParameters( AudioEncodeSourceFormat_t srcAudioFormat, int audioSampleRate, AudioEncodeOptions_t audioOptions, int audioSampleGroupSize ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::SetMovieSourceAudioParameters()\n"); |
|
#endif |
|
|
|
SetResult( VideoResult::ILLEGAL_OPERATION ); |
|
AssertExitF( m_bHasAudio ); |
|
|
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
AssertExitF( IS_IN_RANGECOUNT( srcAudioFormat, AudioEncodeSourceFormat::AUDIO_NONE, AudioEncodeSourceFormat::AUDIO_FORMAT_COUNT ) ); |
|
AssertExitF( audioSampleRate == 0 || IS_IN_RANGE( audioSampleRate, cMinSampleRate, cMaxSampleRate ) ); |
|
|
|
SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE ); |
|
|
|
m_audioSampleRate = audioSampleRate; |
|
m_audioSampleGroupSize = audioSampleGroupSize; |
|
|
|
// now setup the audio |
|
vorbis_info_init(&m_vi); |
|
|
|
// We are always using 128kbps for the audio |
|
int ret=vorbis_encode_init(&m_vi,m_audioChannels,m_audioSampleRate,-1,WEBM_AUDIO_BITRATE,-1); |
|
if (ret) |
|
{ |
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
return false; |
|
} |
|
/* set up the analysis state and auxiliary encoding storage */ |
|
vorbis_comment_init(&m_vc); |
|
|
|
// get the app name |
|
char appname[ 256 ]; |
|
KeyValues *modinfo = new KeyValues( "ModInfo" ); |
|
|
|
if ( modinfo->LoadFromFile( g_pFullFileSystem, "gameinfo.txt" ) ) |
|
Q_strncpy( appname, modinfo->GetString( "game" ), sizeof( appname ) ); |
|
else |
|
Q_strncpy( appname, "Source1 Game", sizeof( appname ) ); |
|
|
|
modinfo->deleteThis(); |
|
modinfo = NULL; |
|
|
|
vorbis_comment_add_tag(&m_vc,"ENCODER",appname); |
|
|
|
vorbis_analysis_init(&m_vd,&m_vi); |
|
vorbis_block_init(&m_vd,&m_vb); |
|
|
|
// setup the audio track |
|
m_aud_track = m_mkvMuxerSegment.AddAudioTrack(static_cast<int>(m_audioSampleRate), |
|
static_cast<int>(m_audioChannels), |
|
0); |
|
if (!m_aud_track) |
|
{ |
|
printf("\n Could not add audio track.\n"); |
|
return false; |
|
} |
|
|
|
mkvmuxer::AudioTrack* const audio = |
|
static_cast<mkvmuxer::AudioTrack*>( |
|
m_mkvMuxerSegment.GetTrackByNumber(m_aud_track)); |
|
if (!audio) |
|
{ |
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
return false; |
|
} |
|
|
|
/* Vorbis streams begin with three headers; the initial header (with |
|
most of the codec setup parameters) which is mandated by the Ogg |
|
bitstream spec. The second header holds any comment fields. The |
|
third header holds the bitstream codebook. We merely need to |
|
make the headers, then pass them to libvorbis one at a time; |
|
libvorbis handles the additional Ogg bitstream constraints */ |
|
|
|
{ |
|
ogg_packet ident_packet; |
|
ogg_packet comments_packet; |
|
ogg_packet setup_packet; |
|
int iHeaderLength; |
|
uint8 *privateHeader=NULL; |
|
uint8 *pbPrivateHeader=NULL; |
|
|
|
vorbis_analysis_headerout(&m_vd,&m_vc,&ident_packet,&comments_packet,&setup_packet); |
|
iHeaderLength = 3 + ident_packet.bytes + comments_packet.bytes + setup_packet.bytes; |
|
privateHeader = new uint8[iHeaderLength]; |
|
pbPrivateHeader = privateHeader; |
|
|
|
*pbPrivateHeader++ = 2; // number of headers - 1 |
|
*pbPrivateHeader++ = (uint8)ident_packet.bytes; |
|
*pbPrivateHeader++ = (uint8)comments_packet.bytes; |
|
|
|
memcpy(pbPrivateHeader, ident_packet.packet, ident_packet.bytes); |
|
pbPrivateHeader+= ident_packet.bytes; |
|
|
|
memcpy(pbPrivateHeader, comments_packet.packet, comments_packet.bytes); |
|
pbPrivateHeader+= comments_packet.bytes; |
|
|
|
memcpy(pbPrivateHeader, setup_packet.packet, setup_packet.bytes); |
|
pbPrivateHeader+= setup_packet.bytes; |
|
|
|
audio->SetCodecPrivate(privateHeader,iHeaderLength); |
|
delete [] privateHeader; |
|
} |
|
|
|
SetResult( VideoResult::SUCCESS ); |
|
return true; |
|
} |
|
|
|
|
|
bool CWebMVideoRecorder::IsReadyToRecord() |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::IsReadyToRecord()\n"); |
|
#endif |
|
|
|
return ( m_SrcImageYV12Buffer != NULL && !m_bMovieFinished); |
|
} |
|
|
|
|
|
VideoResult_t CWebMVideoRecorder::GetLastResult() |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::GetLastResult()\n"); |
|
#endif |
|
|
|
return m_LastResult; |
|
} |
|
|
|
|
|
void CWebMVideoRecorder::SetResult( VideoResult_t resultCode ) |
|
{ |
|
m_LastResult = resultCode; |
|
} |
|
|
|
void CWebMVideoRecorder::ConvertBGRAToYV12( void *pFrameBuffer, int nStrideAdjustBytes, vpx_image_t *m_SrcImageYV12Buffer, bool fIncludesAlpha ) |
|
{ |
|
int iSrcBytesPerPixel; |
|
|
|
// Handle 32-bit with alpha and 24-bit |
|
if (fIncludesAlpha) |
|
iSrcBytesPerPixel = 4; |
|
else |
|
iSrcBytesPerPixel = 3; |
|
|
|
int srcStride = m_SrcImageWidth * iSrcBytesPerPixel + nStrideAdjustBytes; |
|
int iX,iY; |
|
byte *pSrc; |
|
byte *pDstY,*pDstU,*pDstV; |
|
byte r,g,b,a; |
|
byte y,u,v; |
|
|
|
// This isn't fast or good, but it works and that's good enough for a first pass |
|
pSrc = (byte *)pFrameBuffer; |
|
|
|
// YV12 has a complete frame of Y, followed by half sized U and V |
|
pDstY = m_SrcImageYV12Buffer->planes[0]; |
|
pDstU = m_SrcImageYV12Buffer->planes[1]; |
|
pDstV = m_SrcImageYV12Buffer->planes[2]; |
|
|
|
|
|
for (iY=0;iY<m_MovieFrameHeight;iY++) |
|
{ |
|
for(iX=0;iX<m_MovieFrameWidth;iX++) |
|
{ |
|
b = pSrc[iSrcBytesPerPixel*iX+0]; |
|
g = pSrc[iSrcBytesPerPixel*iX+1]; |
|
r = pSrc[iSrcBytesPerPixel*iX+2]; |
|
if (fIncludesAlpha) |
|
a = pSrc[iSrcBytesPerPixel*iX+3]; |
|
|
|
y = (byte)((66*r + 129*g + 25*b + 128) >> 8) + 16; |
|
|
|
pDstY[iX] = y; |
|
if ((iY%2 == 0) && (iX%2 == 0)) |
|
{ |
|
u = (byte)((-38*r - 74*g + 112*b + 128) >> 8) + 128; |
|
v = (byte)((112*r - 94*g - 18*b + 128) >> 8) + 128; |
|
|
|
pDstU[iX/2] = u; |
|
pDstV[iX/2] = v; |
|
} |
|
} |
|
// next row, using strides |
|
pDstY += m_SrcImageYV12Buffer->stride[0]; |
|
if ((iY%2) == 0) |
|
{ |
|
pDstU += m_SrcImageYV12Buffer->stride[1]; |
|
pDstV += m_SrcImageYV12Buffer->stride[2]; |
|
} |
|
pSrc += srcStride; |
|
} |
|
|
|
} |
|
|
|
bool CWebMVideoRecorder::AppendVideoFrame( void *pFrameBuffer, int nStrideAdjustBytes ) |
|
{ |
|
uint64 time_ns; |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::AppendVideoFrame()\n"); |
|
#endif |
|
|
|
// $$$DEBUG Dump the Frame $$$DEBUG |
|
// If you are trying to debug how the webm libraries work it's very difficult to use the full TF2 replay system as a testbed |
|
// so this section of code here and in the AppendAudioSamples writes out the data TF2 would send to the video recorder to |
|
// plain files on the disc. You can then use those files with an extracted framework to work with the webm libraries |
|
#ifdef NEVER |
|
{ |
|
FILE *fp=NULL; |
|
char rgch[256]; |
|
int i,j; |
|
byte *pByte; |
|
|
|
sprintf(rgch, "./frames/vid_%d", m_nFramesAdded); |
|
fp = fopen(rgch, "wb"); |
|
|
|
pByte = (byte *)pFrameBuffer; |
|
for (i=0;i<m_MovieFrameHeight;i++) |
|
{ |
|
fwrite(pByte, 4, m_MovieFrameWidth, fp); |
|
pByte += (m_MovieFrameWidth*4 + nStrideAdjustBytes); |
|
} |
|
fclose(fp); |
|
|
|
} |
|
#endif |
|
|
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
AssertExitF( pFrameBuffer != nullptr ); |
|
|
|
SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE ); |
|
AssertExitF( IsReadyToRecord() ); |
|
|
|
// Convert the frame in pFrameBuffer into YV12 and add it to the stream |
|
// only convert BGRA_32BIT and BGR_24BIT right now |
|
switch(m_SrcImageFormat) |
|
{ |
|
case VideoEncodeSourceFormat::BGR_24BIT: |
|
ConvertBGRAToYV12(pFrameBuffer, nStrideAdjustBytes, m_SrcImageYV12Buffer, false); |
|
break; |
|
case VideoEncodeSourceFormat::BGRA_32BIT: |
|
ConvertBGRAToYV12(pFrameBuffer, nStrideAdjustBytes, m_SrcImageYV12Buffer, true); |
|
break; |
|
default: |
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
return false; |
|
} |
|
|
|
|
|
// Compress it with the webm codec |
|
|
|
time_ns = ((uint64)m_FrameDuration*(uint64)(m_nFramesAdded+1)); |
|
|
|
vpx_codec_err_t vpxError = vpx_codec_encode(&m_vpxContext, m_SrcImageYV12Buffer, time_ns, m_FrameDuration, 0, 0); |
|
|
|
if (vpxError != VPX_CODEC_OK) |
|
{ |
|
SetResult( VideoResult::VIDEO_ERROR_OCCURED ); |
|
return false; |
|
} |
|
|
|
// Add this frame to the stream |
|
vpx_codec_iter_t vpxIter = NULL; |
|
const vpx_codec_cx_pkt_t *vpxPacket; |
|
bool bKeyframe=false; |
|
|
|
while ( ( vpxPacket = vpx_codec_get_cx_data(&m_vpxContext, &vpxIter)) != NULL ) |
|
{ |
|
if (vpxPacket->kind == VPX_CODEC_CX_FRAME_PKT) |
|
{ |
|
// Extract if this is a keyframe from the first packet of data for each frame |
|
bKeyframe = vpxPacket->data.frame.flags & VPX_FRAME_IS_KEY; |
|
|
|
m_mkvMuxerSegment.AddFrame((const uint8 *)vpxPacket->data.frame.buf, vpxPacket->data.frame.sz, m_vid_track, |
|
time_ns, bKeyframe); |
|
} |
|
|
|
} |
|
m_nFramesAdded++; |
|
|
|
SetResult( VideoResult::SUCCESS ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CWebMVideoRecorder::FlushAudioSamples() |
|
{ |
|
ogg_packet op; |
|
|
|
// See if there are any samples available |
|
|
|
while (vorbis_analysis_blockout(&m_vd, &m_vb) == 1) |
|
{ |
|
int status_analysis = vorbis_analysis(&m_vb, NULL); |
|
if (status_analysis) |
|
{ |
|
return false; |
|
} |
|
status_analysis = vorbis_bitrate_addblock(&m_vb); |
|
if (status_analysis) |
|
{ |
|
return false; |
|
} |
|
while ((status_analysis = vorbis_bitrate_flushpacket(&m_vd, &op)) > 0) |
|
{ |
|
// Add this packet to the webm stream |
|
uint64 time_ns = ((uint64)m_FrameDuration*(uint64)(m_nFramesAdded+1)); |
|
|
|
if (!m_mkvMuxerSegment.AddFrame(op.packet, |
|
op.bytes, |
|
m_aud_track, |
|
time_ns, |
|
true)) |
|
{ |
|
return false; |
|
} |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
bool CWebMVideoRecorder::AppendAudioSamples( void *pSampleBuffer, size_t sampleSize ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::AppendAudioSamples()\n"); |
|
#endif |
|
|
|
SetResult( VideoResult::ILLEGAL_OPERATION ); |
|
AssertExitF( m_bHasAudio ); |
|
|
|
SetResult( VideoResult::BAD_INPUT_PARAMETERS ); |
|
AssertExitF( pSampleBuffer != nullptr ); |
|
|
|
SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE ); |
|
AssertExitF( IsReadyToRecord() ); |
|
|
|
// $$$DEBUG Dump the Audio $$$DEBUG |
|
// The audio version of the dumping of video/audio data to files for debugging |
|
// You may need to change the path to put these files where you want them. |
|
#ifdef NEVER |
|
{ |
|
FILE *fp=NULL; |
|
char rgch[256]; |
|
int i,j; |
|
byte *pByte; |
|
static int i_AudSample_batch=0; |
|
static int i_AudSample_frame=-1; |
|
|
|
if (m_nFramesAdded != i_AudSample_frame) |
|
i_AudSample_batch = 0; |
|
|
|
i_AudSample_frame = m_nFramesAdded; |
|
|
|
sprintf(rgch, "./frames/aud_%d_%d", i_AudSample_frame, i_AudSample_batch); |
|
fp = fopen(rgch, "wb"); |
|
|
|
// Size |
|
fwrite(&sampleSize, sizeof(sampleSize), 1, fp); |
|
|
|
// Data |
|
pByte = (byte *)pSampleBuffer; |
|
fwrite(pByte, sampleSize, 1, fp); |
|
|
|
fclose(fp); |
|
|
|
i_AudSample_batch++; |
|
|
|
} |
|
#endif |
|
int num_blocks = sampleSize / ((m_audioBitDepth/8)*m_audioChannels); |
|
float **buffer=vorbis_analysis_buffer(&m_vd,num_blocks); |
|
|
|
// Deinterleave input samples, convert them to float, and store them in |
|
// buffer |
|
const int16* pPCMsamples = (int16*)pSampleBuffer; |
|
|
|
for (int i = 0; i < num_blocks; ++i) |
|
{ |
|
for (int c = 0; c < m_audioChannels; ++c) |
|
{ |
|
buffer[c][i] = pPCMsamples[i * m_audioChannels + c] / 32768.f; |
|
} |
|
} |
|
|
|
vorbis_analysis_wrote(&m_vd, num_blocks); |
|
|
|
FlushAudioSamples(); |
|
|
|
return true; |
|
} |
|
|
|
|
|
int CWebMVideoRecorder::GetFrameCount() |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::GetFrameCount()\n"); |
|
#endif |
|
|
|
return m_nFramesAdded; |
|
} |
|
|
|
|
|
int CWebMVideoRecorder::GetSampleCount() |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::GetSampleCount()\n"); |
|
#endif |
|
|
|
// return ( m_pEncoder == nullptr ) ? 0 : m_pEncoder->GetSampleCount(); |
|
return true; |
|
} |
|
|
|
|
|
VideoFrameRate_t CWebMVideoRecorder::GetFPS() |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::GetFPS()\n"); |
|
#endif |
|
|
|
return m_MovieRecordFPS; |
|
} |
|
|
|
|
|
int CWebMVideoRecorder::GetSampleRate() |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::GetSampleRate()\n"); |
|
#endif |
|
// return ( m_pEncoder == nullptr ) ? 0 : m_pEncoder->GetSampleRate(); |
|
return true; |
|
} |
|
|
|
|
|
bool CWebMVideoRecorder::AbortMovie() |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::AbortMovie()\n"); |
|
#endif |
|
SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE ); |
|
// AssertExitF( m_pEncoder != nullptr && !m_bMovieFinished ); |
|
|
|
m_bMovieFinished = true; |
|
// return m_pEncoder->AbortMovie(); |
|
return true; |
|
} |
|
|
|
|
|
bool CWebMVideoRecorder::FinishMovie( bool SaveMovieToDisk ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::FinishMovie()\n"); |
|
#endif |
|
// Have to finish out the compress with a NULL frame |
|
// Compress it with the webm codec |
|
m_nFramesAdded++; |
|
uint64 time_ns = ((uint64)m_FrameDuration*(uint64)(m_nFramesAdded+1)); |
|
|
|
vpx_codec_err_t vpxError = vpx_codec_encode(&m_vpxContext, NULL, time_ns, m_FrameDuration, 0, 0); |
|
|
|
if (vpxError != VPX_CODEC_OK) |
|
{ |
|
return false; |
|
} |
|
|
|
// Add this frame to the stream |
|
vpx_codec_iter_t vpxIter = NULL; |
|
const vpx_codec_cx_pkt_t *vpxPacket; |
|
|
|
while ( ( vpxPacket = vpx_codec_get_cx_data(&m_vpxContext, &vpxIter) ) != NULL ) |
|
{ |
|
if (vpxPacket->kind == VPX_CODEC_CX_FRAME_PKT) |
|
{ |
|
uint64 time_ns; |
|
bool bKeyframe=false; |
|
|
|
// Extract if this is a keyframe from the first packet of data for each frame |
|
bKeyframe = vpxPacket->data.frame.flags & VPX_FRAME_IS_KEY; |
|
time_ns = ((uint64)m_FrameDuration*(uint64)m_nFramesAdded); |
|
|
|
m_mkvMuxerSegment.AddFrame((const uint8 *)vpxPacket->data.frame.buf, vpxPacket->data.frame.sz, m_vid_track, time_ns, bKeyframe); |
|
} |
|
} |
|
|
|
m_mkvMuxerSegment.Finalize(); |
|
m_mkvWriter.Close(); |
|
|
|
vorbis_dsp_clear(&m_vd); |
|
vorbis_block_clear(&m_vb); |
|
vorbis_info_clear(&m_vi); |
|
|
|
m_bMovieFinished = true; |
|
|
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::FinishMovie() movie encoded.\n"); |
|
Msg("Total frames written: %d Time for movie in nanoseconds: %lu\n", m_nFramesAdded, ((unsigned long)m_nFramesAdded*m_FrameDuration)); |
|
#endif |
|
|
|
SetResult( VideoResult::SUCCESS ); |
|
|
|
return true; |
|
} |
|
|
|
bool CWebMVideoRecorder::EstimateMovieFileSize( size_t *pEstSize, int movieWidth, int movieHeight, VideoFrameRate_t movieFps, float movieDuration, VideoEncodeCodec_t theCodec, int videoQuality, AudioEncodeSourceFormat_t srcAudioFormat, int audioSampleRate ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::EstimateMovieFileSize()\n"); |
|
#endif |
|
float fVidRate; |
|
float fAudRate; |
|
float movieDurationInSeconds; |
|
|
|
fVidRate = GetVideoDataRate(videoQuality, movieWidth, movieHeight); |
|
fAudRate = GetAudioDataRate(videoQuality, movieWidth, movieHeight); |
|
movieDurationInSeconds = movieDuration; |
|
|
|
// data rates is in killobits/second so convert to bytes/second |
|
*pEstSize = (size_t)((fVidRate*1000*movieDurationInSeconds/8) + (fAudRate*1000*movieDuration*movieDurationInSeconds/8)); |
|
|
|
SetResult( VideoResult::SUCCESS ); |
|
return true; |
|
} |
|
|
|
|
|
float CWebMVideoRecorder::GetVideoDataRate( int quality, int width, int height ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::GetDataRate()\n"); |
|
#endif |
|
for(int i = 0; i< ARRAYSIZE( s_WebMEncodeRates ); i++ ) |
|
{ |
|
if (s_WebMEncodeRates[i].m_XResolution == width && s_WebMEncodeRates[i].m_YResolution == height) |
|
{ |
|
return s_WebMEncodeRates[i].m_MinDataRate + (((s_WebMEncodeRates[i].m_MaxDataRate - s_WebMEncodeRates[i].m_MinDataRate)*(float)quality)/100.0); |
|
} |
|
} |
|
// Didn't find the resolution, odd |
|
Msg("Unable to find WebM resolution (%d, %d) at quality %d\n", width, height, quality); |
|
// Default to 2kb/s |
|
return 2000.0f; |
|
} |
|
|
|
float CWebMVideoRecorder::GetAudioDataRate( int quality, int width, int height ) |
|
{ |
|
#ifdef LOG_ENCODER_OPERATIONS |
|
Msg("CWebMVideoRecorder::GetDataRate()\n"); |
|
#endif |
|
for(int i = 0; i< ARRAYSIZE( s_WebMEncodeRates ); i++ ) |
|
{ |
|
if (s_WebMEncodeRates[i].m_XResolution == width && s_WebMEncodeRates[i].m_YResolution == height) |
|
{ |
|
return s_WebMEncodeRates[i].m_MinAudioDataRate + (((s_WebMEncodeRates[i].m_MaxAudioDataRate - s_WebMEncodeRates[i].m_MinAudioDataRate)*(float)quality)/100.0); |
|
} |
|
} |
|
// Didn't find the resolution, odd |
|
Msg("Unable to find WebM resolution (%d, %d) at quality %d\n", width, height, quality); |
|
|
|
// Default to 128kb/s for audio |
|
return 128.0f; |
|
}
|
|
|