diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /video/webm_recorder.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'video/webm_recorder.cpp')
| -rw-r--r-- | video/webm_recorder.cpp | 811 |
1 files changed, 811 insertions, 0 deletions
diff --git a/video/webm_recorder.cpp b/video/webm_recorder.cpp new file mode 100644 index 0000000..e5d46c3 --- /dev/null +++ b/video/webm_recorder.cpp @@ -0,0 +1,811 @@ +//========= 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; +} |