From 54fcaf4bafca40035e98c5b0df7993e3da4e2677 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Mon, 6 Sep 2021 15:48:42 -0400 Subject: [PATCH 01/19] Libav integration in work. --- src/CMakeLists.txt | 9 +- src/drivers/Qt/AviRecord.cpp | 366 ++++++++++++++++++++++++++++++++++- src/drivers/Qt/AviRecord.h | 3 + 3 files changed, 371 insertions(+), 7 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 797bf066..9023a8f9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -90,6 +90,13 @@ else(WIN32) add_definitions( -D_USE_X265 ${X265_CFLAGS} ) endif() + pkg_check_modules( LIBAV libavcodec libavformat libavutil libavresample libswresample) + + if ( ${LIBAV_FOUND} ) + message( STATUS "Using System Libav Library ${LIBAV_VERSION}" ) + add_definitions( -D_USE_LIBAV ${LIBAV_CFLAGS} ) + endif() + #pkg_check_modules( GL gl) # Use built in find package instead for OpenGL # Check for OpenGL @@ -547,7 +554,7 @@ target_link_libraries( ${APP_NAME} ${OPENGL_LDFLAGS} ${SDL2_LDFLAGS} ${MINIZIP_LDFLAGS} ${ZLIB_LIBRARIES} - ${LUA_LDFLAGS} ${X264_LDFLAGS} ${X265_LDFLAGS} + ${LUA_LDFLAGS} ${X264_LDFLAGS} ${X265_LDFLAGS} ${LIBAV_LDFLAGS} ${SYS_LIBS} ) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 2340fe6a..ba16d87d 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -43,6 +43,16 @@ #ifdef _USE_X265 #include "x265.h" #endif +#ifdef _USE_LIBAV +#ifdef __cplusplus +extern "C" +{ +#include "libavutil/opt.h" +#include "libavformat/avformat.h" +#include "libavresample/avresample.h" +} +#endif +#endif #include "Qt/AviRecord.h" #include "Qt/avi/gwavi.h" @@ -689,6 +699,318 @@ static int encode_frame( unsigned char *inBuf, int width, int height ) } // End namespace VFW #endif //************************************************************************************** +// LIBAV Interface +#ifdef _USE_LIBAV +namespace LIBAV +{ +static AVFormatContext *oc = NULL; + +struct OutputStream +{ + AVStream *st; + AVCodecContext *enc; + AVFrame *frame; + AVFrame *tmp_frame; + AVAudioResampleContext *avr; + + OutputStream(void) + { + st = NULL; + enc = NULL; + frame = tmp_frame = NULL; + avr = NULL; + } +}; +static OutputStream video_st; +static OutputStream audio_st; + +static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height) +{ + AVFrame *picture; + int ret; + picture = av_frame_alloc(); + if (!picture) + return NULL; + picture->format = pix_fmt; + picture->width = width; + picture->height = height; + /* allocate the buffers for the frame data */ + ret = av_frame_get_buffer(picture, 32); + if (ret < 0) + { + fprintf(stderr, "Could not allocate frame data.\n"); + return NULL; + } + return picture; +} + +static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) +{ + int ret; + AVCodec *codec; + AVCodecContext *c; + + /* find the video encoder */ + codec = avcodec_find_encoder(codec_id); + + if (codec == NULL) + { + fprintf(stderr, "codec not found\n"); + return -1; + } + + ost->st = avformat_new_stream(oc, NULL); + + if (ost->st == NULL) + { + fprintf(stderr, "Could not alloc stream\n"); + return -1; + } + + c = avcodec_alloc_context3(codec); + + if (c == NULL) + { + fprintf(stderr, "Could not alloc an encoding context\n"); + return -1; + } + ost->enc = c; + + /* Put sample parameters. */ + c->bit_rate = 400000; + /* Resolution must be a multiple of two. */ + c->width = nes_shm->video.ncol; + c->height = nes_shm->video.nrow; + + /* timebase: This is the fundamental unit of time (in seconds) in terms + * of which frame timestamps are represented. For fixed-fps content, + * timebase should be 1/framerate and timestamp increments should be + * identical to 1. */ + ost->st->time_base = (AVRational){ 1, 60 }; + c->time_base = ost->st->time_base; + c->gop_size = 12; /* emit one intra frame every twelve frames at most */ + c->pix_fmt = AV_PIX_FMT_YUV420P; + + if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) + { + /* just for testing, we also add B-frames */ + c->max_b_frames = 2; + } + if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) + { + /* Needed to avoid using macroblocks in which some coeffs overflow. + * This does not happen with normal video, it just happens here as + * the motion of the chroma plane does not match the luma plane. */ + c->mb_decision = 2; + } + /* Some formats want stream headers to be separate. */ + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + { + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + /* open the codec */ + if (avcodec_open2(c, NULL, NULL) < 0) + { + fprintf(stderr, "could not open codec\n"); + return -1; + } + + /* Allocate the encoded raw picture. */ + ost->frame = alloc_picture(c->pix_fmt, c->width, c->height); + + if (!ost->frame) + { + fprintf(stderr, "Could not allocate picture\n"); + return -1; + } + + /* If the output format is not YUV420P, then a temporary YUV420P + * picture is needed too. It is then converted to the required + * output format. */ + ost->tmp_frame = NULL; + + if (c->pix_fmt != AV_PIX_FMT_YUV420P) + { + ost->tmp_frame = alloc_picture(AV_PIX_FMT_YUV420P, c->width, c->height); + + if (!ost->tmp_frame) + { + fprintf(stderr, "Could not allocate temporary picture\n"); + return -1; + } + } + + /* copy the stream parameters to the muxer */ + ret = avcodec_parameters_from_context(ost->st->codecpar, c); + + if (ret < 0) + { + fprintf(stderr, "Could not copy the stream parameters\n"); + return -1; + } + return 0; +} + +static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) +{ + int ret; + AVCodec *codec; + AVCodecContext *c; + + /* find the audio encoder */ + codec = avcodec_find_encoder(codec_id); + + if (codec == NULL) + { + fprintf(stderr, "codec not found\n"); + return -1; + } + + ost->st = avformat_new_stream(oc, NULL); + + if (ost->st == NULL) + { + fprintf(stderr, "Could not alloc stream\n"); + return -1; + } + + c = avcodec_alloc_context3(codec); + + if (c == NULL) + { + fprintf(stderr, "Could not alloc an encoding context\n"); + return -1; + } + ost->enc = c; + + /* put sample parameters */ + c->sample_fmt = codec->sample_fmts ? codec->sample_fmts[0] : AV_SAMPLE_FMT_S16; + c->sample_rate = codec->supported_samplerates ? codec->supported_samplerates[0] : audioSampleRate; + c->channel_layout = codec->channel_layouts ? codec->channel_layouts[0] : AV_CH_LAYOUT_STEREO; + c->channels = av_get_channel_layout_nb_channels(c->channel_layout); + c->bit_rate = 64000; + ost->st->time_base = (AVRational){ 1, c->sample_rate }; + // some formats want stream headers to be separate + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + { + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + /* initialize sample format conversion; + * to simplify the code, we always pass the data through lavr, even + * if the encoder supports the generated format directly -- the price is + * some extra data copying; + */ + ost->avr = avresample_alloc_context(); + if (!ost->avr) + { + fprintf(stderr, "Error allocating the resampling context\n"); + return -1; + } + av_opt_set_int(ost->avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(ost->avr, "in_sample_rate", audioSampleRate, 0); + av_opt_set_int(ost->avr, "in_channel_layout", AV_CH_LAYOUT_MONO, 0); + av_opt_set_int(ost->avr, "out_sample_fmt", c->sample_fmt, 0); + av_opt_set_int(ost->avr, "out_sample_rate", c->sample_rate, 0); + av_opt_set_int(ost->avr, "out_channel_layout", c->channel_layout, 0); + + ret = avresample_open(ost->avr); + if (ret < 0) + { + fprintf(stderr, "Error opening the resampling context\n"); + return -1; + } + + /* open it */ + if (avcodec_open2(c, NULL, NULL) < 0) + { + fprintf(stderr, "could not open codec\n"); + return -1; + } + return 0; +} + +static int initMedia( const char *filename ) +{ + AVOutputFormat *fmt; + + /* Initialize libavcodec, and register all codecs and formats. */ + av_register_all(); + + /* Autodetect the output format from the name. default is MPEG. */ + fmt = av_guess_format(NULL, filename, NULL); + + if (fmt == NULL) + { + printf("Could not deduce output format from file extension: using MPEG.\n"); + fmt = av_guess_format("mpeg", NULL, NULL); + } + if (fmt == NULL) + { + fprintf(stderr, "Could not find suitable output format\n"); + return -1; + } + + /* Allocate the output media context. */ + oc = avformat_alloc_context(); + if (oc == NULL) + { + fprintf(stderr, "Memory error\n"); + return -1; + } + oc->oformat = fmt; + + //strncpy(oc->filename, filename, sizeof(oc->filename)); + + if ( initVideoStream( fmt->video_codec, &video_st ) ) + { + return -1; + } + if ( fmt->audio_codec == AV_CODEC_ID_NONE ) + { + fmt->audio_codec = AV_CODEC_ID_PCM_S16LE; + } + if ( initAudioStream( fmt->audio_codec, &audio_st ) ) + { + return -1; + } + + av_dump_format(oc, 0, filename, 1); + + /* open the output file, if needed */ + if ( !(fmt->flags & AVFMT_NOFILE)) + { + if (avio_open( &oc->pb, filename, AVIO_FLAG_WRITE) < 0) + { + fprintf(stderr, "Could not open '%s'\n", filename); + return -1; + } + else + { + printf("Opened: %s\n", filename); + } + } + + /* Write the stream header, if any. */ + avformat_write_header(oc, NULL); + + return 0; +} + +static int init( int width, int height ) +{ + return 0; +} + +static int close(void) +{ + + return 0; +} + +} // End namespace LIBAV +#endif +//************************************************************************************** int aviRecordOpenFile( const char *filepath ) { char fourcc[8]; @@ -806,14 +1128,29 @@ int aviRecordOpenFile( const char *filepath ) } #endif - gwavi = new gwavi_t(); - - if ( gwavi->open( fileName, nes_shm->video.ncol, nes_shm->video.nrow, fourcc, fps, &audioConfig ) ) +#ifdef _USE_LIBAV + if ( videoFormat == AVI_LIBAV ) { - printf("Error: Failed to open AVI file.\n"); - recordEnable = false; - return -1; + if ( LIBAV::initMedia( fileName ) ) + { + printf("Error: Failed to open AVI file.\n"); + recordEnable = false; + return -1; + } } + else +#else + { + gwavi = new gwavi_t(); + + if ( gwavi->open( fileName, nes_shm->video.ncol, nes_shm->video.nrow, fourcc, fps, &audioConfig ) ) + { + printf("Error: Failed to open AVI file.\n"); + recordEnable = false; + return -1; + } + } +#endif vbufSize = 1024 * 1024 * 60; rawVideoBuf = (uint32_t*)malloc( vbufSize * sizeof(uint32_t) ); @@ -1022,6 +1359,11 @@ int FCEUD_AviGetFormatOpts( std::vector &formatList ) s.assign("X265 (H.265)"); break; #endif + #ifdef _USE_LIBAV + case AVI_LIBAV: + s.assign("libav (ffmpeg)"); + break; + #endif #ifdef WIN32 case AVI_VFW: s.assign("VfW (Video for Windows)"); @@ -1096,6 +1438,12 @@ void AviRecordDiskThread_t::run(void) X265::init( width, height ); } #endif +#ifdef _USE_LIBAV + if ( localVideoFormat == AVI_LIBAV) + { + LIBAV::init( width, height ); + } +#endif #ifdef WIN32 if ( localVideoFormat == AVI_VFW) { @@ -1203,6 +1551,12 @@ void AviRecordDiskThread_t::run(void) X265::close(); } #endif +#ifdef _USE_LIBAV + if ( localVideoFormat == AVI_LIBAV) + { + LIBAV::close(); + } +#endif #ifdef WIN32 if ( localVideoFormat == AVI_VFW) { diff --git a/src/drivers/Qt/AviRecord.h b/src/drivers/Qt/AviRecord.h index 4bd92bc7..11d68cb1 100644 --- a/src/drivers/Qt/AviRecord.h +++ b/src/drivers/Qt/AviRecord.h @@ -21,6 +21,9 @@ enum aviEncoderList #ifdef _USE_X265 AVI_X265, #endif + #ifdef _USE_LIBAV + AVI_LIBAV, + #endif #ifdef WIN32 AVI_VFW, #endif From 80df18351a434acbcd838287a4cf52b0ac3cec41 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Tue, 7 Sep 2021 00:10:30 -0400 Subject: [PATCH 02/19] libav recording in work. --- src/drivers/Qt/AviRecord.cpp | 251 +++++++++++++++++++++++++++++++---- 1 file changed, 224 insertions(+), 27 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index ba16d87d..ce07e4bf 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -720,6 +720,22 @@ struct OutputStream frame = tmp_frame = NULL; avr = NULL; } + + void close(void) + { + if ( enc != NULL ) + { + avcodec_free_context(&enc); enc = NULL; + } + if ( frame != NULL ) + { + av_frame_free(&frame); frame = NULL; + } + if ( tmp_frame != NULL ) + { + av_frame_free(&tmp_frame); tmp_frame = NULL; + } + } }; static OutputStream video_st; static OutputStream audio_st; @@ -730,17 +746,21 @@ static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height) int ret; picture = av_frame_alloc(); if (!picture) + { return NULL; + } picture->format = pix_fmt; picture->width = width; picture->height = height; + picture->pts = 0; /* allocate the buffers for the frame data */ - ret = av_frame_get_buffer(picture, 32); + ret = av_frame_get_buffer(picture, 0); if (ret < 0) { fprintf(stderr, "Could not allocate frame data.\n"); return NULL; } + return picture; } @@ -758,6 +778,7 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) fprintf(stderr, "codec not found\n"); return -1; } + printf("CODEC: %s\n", codec->name ); ost->st = avformat_new_stream(oc, NULL); @@ -791,6 +812,8 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = AV_PIX_FMT_YUV420P; + printf("PIX_FMT:%i\n", c->pix_fmt ); + if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) { /* just for testing, we also add B-frames */ @@ -830,16 +853,16 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) * output format. */ ost->tmp_frame = NULL; - if (c->pix_fmt != AV_PIX_FMT_YUV420P) - { - ost->tmp_frame = alloc_picture(AV_PIX_FMT_YUV420P, c->width, c->height); + //if (c->pix_fmt != AV_PIX_FMT_RGB24) + //{ + // ost->tmp_frame = alloc_picture(AV_PIX_FMT_RGB24, c->width, c->height); - if (!ost->tmp_frame) - { - fprintf(stderr, "Could not allocate temporary picture\n"); - return -1; - } - } + // if (ost->tmp_frame == NULL) + // { + // fprintf(stderr, "Could not allocate temporary picture\n"); + // return -1; + // } + //} /* copy the stream parameters to the muxer */ ret = avcodec_parameters_from_context(ost->st->codecpar, c); @@ -852,9 +875,37 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) return 0; } +static AVFrame *alloc_audio_frame(enum AVSampleFormat sample_fmt, + uint64_t channel_layout, + int sample_rate, int nb_samples) +{ + AVFrame *frame = av_frame_alloc(); + int ret; + if (!frame) + { + fprintf(stderr, "Error allocating an audio frame\n"); + return NULL; + } + frame->format = sample_fmt; + frame->channel_layout = channel_layout; + frame->sample_rate = sample_rate; + frame->nb_samples = nb_samples; + + if (nb_samples) + { + ret = av_frame_get_buffer(frame, 0); + if (ret < 0) + { + fprintf(stderr, "Error allocating an audio buffer\n"); + return NULL; + } + } + return frame; +} + static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) { - int ret; + int ret, nb_samples; AVCodec *codec; AVCodecContext *c; @@ -927,6 +978,26 @@ static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) fprintf(stderr, "could not open codec\n"); return -1; } + + if (c->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) + { + nb_samples = 10000; + } + else + { + nb_samples = c->frame_size; + } + + ost->frame = alloc_audio_frame(c->sample_fmt, c->channel_layout, c->sample_rate, nb_samples); + ost->tmp_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, AV_CH_LAYOUT_MONO, audioSampleRate, nb_samples); + + /* copy the stream parameters to the muxer */ + ret = avcodec_parameters_from_context(ost->st->codecpar, c); + if (ret < 0) + { + fprintf(stderr, "Could not copy the stream parameters\n"); + return -1; + } return 0; } @@ -966,10 +1037,10 @@ static int initMedia( const char *filename ) { return -1; } - if ( fmt->audio_codec == AV_CODEC_ID_NONE ) - { - fmt->audio_codec = AV_CODEC_ID_PCM_S16LE; - } + //if ( fmt->audio_codec == AV_CODEC_ID_NONE ) + //{ + // fmt->audio_codec = AV_CODEC_ID_PCM_S16LE; + //} if ( initAudioStream( fmt->audio_codec, &audio_st ) ) { return -1; @@ -992,7 +1063,10 @@ static int initMedia( const char *filename ) } /* Write the stream header, if any. */ - avformat_write_header(oc, NULL); + if ( avformat_write_header(oc, NULL) ) + { + return -1; + } return 0; } @@ -1002,8 +1076,116 @@ static int init( int width, int height ) return 0; } +static int encode_video_frame( unsigned char *inBuf ) +{ + int ret, w2, h2, x, y, ofs; + //int luma_size, chroma_size; + AVCodecContext *c = video_st.enc; + + ret = av_frame_make_writable( video_st.frame ); + + if ( ret < 0 ) + { + return -1; + } + + //luma_size = c->width * c->height; + //chroma_size = luma_size / 4; + + //printf("Luma:%i Chroma:%i \n", luma_size, chroma_size ); + + //for (int i=0; i<3; i++) + //{ + // printf("linesize[%i] = %i \n", i, video_st.frame->linesize[i] ); + //} + ofs = 0; + // Y + for (y = 0; y < c->height; y++) + { + for (x = 0; x < c->width; x++) + { + video_st.frame->data[0][y * video_st.frame->linesize[0] + x] = inBuf[ofs]; ofs++; + } + } + w2 = c->width / 2; + h2 = c->height / 2; + + /* Cb and Cr */ + for (y = 0; y < h2; y++) + { + for (x = 0; x < w2; x++) + { + video_st.frame->data[1][y * video_st.frame->linesize[1] + x] = inBuf[ofs]; ofs++; + } + } + for (y = 0; y < h2; y++) + { + for (x = 0; x < w2; x++) + { + video_st.frame->data[2][y * video_st.frame->linesize[2] + x] = inBuf[ofs]; ofs++; + } + } + //memcpy( video_st.frame->data[0], &inBuf[ofs], luma_size ); ofs += luma_size; + //memcpy( video_st.frame->data[1], &inBuf[ofs], chroma_size ); ofs += chroma_size; + //memcpy( video_st.frame->data[2], &inBuf[ofs], chroma_size ); ofs += chroma_size; + + video_st.frame->pts++; + + //printf("Write Frame: %li\n", video_st.frame->pts); + + /* encode the image */ + ret = avcodec_send_frame(c, video_st.frame); + if (ret < 0) + { + fprintf(stderr, "Error submitting a frame for encoding\n"); + return -1; + } + + while (ret >= 0) + { + AVPacket pkt = { 0 }; + + av_init_packet(&pkt); + + ret = avcodec_receive_packet(c, &pkt); + + if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) + { + fprintf(stderr, "Error encoding a video frame\n"); + return -1; + } + else if (ret >= 0) + { + av_packet_rescale_ts(&pkt, c->time_base, video_st.st->time_base); + pkt.stream_index = video_st.st->index; + /* Write the compressed frame to the media file. */ + ret = av_interleaved_write_frame(oc, &pkt); + if (ret < 0) + { + fprintf(stderr, "Error while writing video frame\n"); + return -1; + } + } + } + + return ret == AVERROR_EOF; +} + static int close(void) { + /* Write the trailer, if any. The trailer must be written before you + * close the CodecContexts open when you wrote the header; otherwise + * av_write_trailer() may try to use memory that was freed on + * av_codec_close(). */ + av_write_trailer(oc); + + /* Close the output file. */ + avio_close(oc->pb); + + /* free the stream */ + avformat_free_context(oc); + + oc = NULL; return 0; } @@ -1139,7 +1321,7 @@ int aviRecordOpenFile( const char *filepath ) } } else -#else +#endif { gwavi = new gwavi_t(); @@ -1150,7 +1332,6 @@ int aviRecordOpenFile( const char *filepath ) return -1; } } -#endif vbufSize = 1024 * 1024 * 60; rawVideoBuf = (uint32_t*)malloc( vbufSize * sizeof(uint32_t) ); @@ -1174,10 +1355,10 @@ int aviRecordAddFrame( void ) return -1; } - if ( gwavi == NULL ) - { - return -1; - } + //if ( gwavi == NULL ) + //{ + // return -1; + //} if ( FCEUI_EmulationPaused() ) { return 0; @@ -1225,10 +1406,10 @@ int aviRecordAddAudioFrame( int32_t *buf, int numSamples ) return -1; } - if ( gwavi == NULL ) - { - return -1; - } + //if ( gwavi == NULL ) + //{ + // return -1; + //} if ( !recordAudio ) { return -1; @@ -1497,6 +1678,13 @@ void AviRecordDiskThread_t::run(void) writeAudio = VFW::encode_frame( rgb24, width, height ) > 0; } #endif + #ifdef _USE_LIBAV + else if ( localVideoFormat == AVI_LIBAV) + { + Convert_4byte_To_I420Frame<4>(videoOut,rgb24,numPixels,width); + LIBAV::encode_video_frame( rgb24 ); + } + #endif else { convertRgb_32_to_24( (const unsigned char*)videoOut, rgb24, @@ -1525,7 +1713,16 @@ void AviRecordDiskThread_t::run(void) if ( numSamples > 0 ) { //printf("NUM Audio Samples: %i \n", numSamples ); - gwavi->add_audio( (unsigned char *)audioOut, numSamples*2); + #ifdef _USE_LIBAV + if ( localVideoFormat == AVI_LIBAV) + { + + } + else + #endif + { + gwavi->add_audio( (unsigned char *)audioOut, numSamples*2); + } numSamples = 0; } From f3ce2457463f5b55ec3669337e4e19b598c618a2 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Tue, 7 Sep 2021 01:41:40 -0400 Subject: [PATCH 03/19] First successful recording of video using libav. --- src/CMakeLists.txt | 2 +- src/drivers/Qt/AviRecord.cpp | 100 ++++++++++++++++------------------- 2 files changed, 47 insertions(+), 55 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9023a8f9..a0bc1f42 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -90,7 +90,7 @@ else(WIN32) add_definitions( -D_USE_X265 ${X265_CFLAGS} ) endif() - pkg_check_modules( LIBAV libavcodec libavformat libavutil libavresample libswresample) + pkg_check_modules( LIBAV libavcodec libavformat libavutil libavresample libswresample libswscale) if ( ${LIBAV_FOUND} ) message( STATUS "Using System Libav Library ${LIBAV_VERSION}" ) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index ce07e4bf..ec972fa1 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -50,6 +50,7 @@ extern "C" #include "libavutil/opt.h" #include "libavformat/avformat.h" #include "libavresample/avresample.h" +#include "libswscale/swscale.h" } #endif #endif @@ -711,6 +712,7 @@ struct OutputStream AVCodecContext *enc; AVFrame *frame; AVFrame *tmp_frame; + struct SwsContext *sws_ctx; AVAudioResampleContext *avr; OutputStream(void) @@ -718,6 +720,7 @@ struct OutputStream st = NULL; enc = NULL; frame = tmp_frame = NULL; + sws_ctx = NULL; avr = NULL; } @@ -735,6 +738,10 @@ struct OutputStream { av_frame_free(&tmp_frame); tmp_frame = NULL; } + if ( avr != NULL ) + { + avresample_free(&avr); avr = NULL; + } } }; static OutputStream video_st; @@ -853,16 +860,22 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) * output format. */ ost->tmp_frame = NULL; - //if (c->pix_fmt != AV_PIX_FMT_RGB24) - //{ - // ost->tmp_frame = alloc_picture(AV_PIX_FMT_RGB24, c->width, c->height); + if (c->pix_fmt != AV_PIX_FMT_BGRA) + { + ost->tmp_frame = alloc_picture(AV_PIX_FMT_BGRA, c->width, c->height); - // if (ost->tmp_frame == NULL) - // { - // fprintf(stderr, "Could not allocate temporary picture\n"); - // return -1; - // } - //} + if (ost->tmp_frame == NULL) + { + fprintf(stderr, "Could not allocate temporary picture\n"); + return -1; + } + } + + ost->sws_ctx = sws_getContext(c->width, c->height, + AV_PIX_FMT_BGRA, + c->width, c->height, + c->pix_fmt, + SWS_BICUBIC, NULL, NULL, NULL); /* copy the stream parameters to the muxer */ ret = avcodec_parameters_from_context(ost->st->codecpar, c); @@ -1034,13 +1047,10 @@ static int initMedia( const char *filename ) //strncpy(oc->filename, filename, sizeof(oc->filename)); if ( initVideoStream( fmt->video_codec, &video_st ) ) + //if ( initVideoStream( AV_CODEC_ID_H264, &video_st ) ) { return -1; } - //if ( fmt->audio_codec == AV_CODEC_ID_NONE ) - //{ - // fmt->audio_codec = AV_CODEC_ID_PCM_S16LE; - //} if ( initAudioStream( fmt->audio_codec, &audio_st ) ) { return -1; @@ -1078,9 +1088,10 @@ static int init( int width, int height ) static int encode_video_frame( unsigned char *inBuf ) { - int ret, w2, h2, x, y, ofs; - //int luma_size, chroma_size; + int ret, y, ofs, inLineSize; + OutputStream *ost = &video_st; AVCodecContext *c = video_st.enc; + unsigned char *outBuf; ret = av_frame_make_writable( video_st.frame ); @@ -1089,50 +1100,25 @@ static int encode_video_frame( unsigned char *inBuf ) return -1; } - //luma_size = c->width * c->height; - //chroma_size = luma_size / 4; - - //printf("Luma:%i Chroma:%i \n", luma_size, chroma_size ); - - //for (int i=0; i<3; i++) - //{ - // printf("linesize[%i] = %i \n", i, video_st.frame->linesize[i] ); - //} ofs = 0; - // Y - for (y = 0; y < c->height; y++) - { - for (x = 0; x < c->width; x++) - { - video_st.frame->data[0][y * video_st.frame->linesize[0] + x] = inBuf[ofs]; ofs++; - } - } - w2 = c->width / 2; - h2 = c->height / 2; - /* Cb and Cr */ - for (y = 0; y < h2; y++) + inLineSize = c->width * 4; + + outBuf = ost->tmp_frame->data[0]; + + for (y=0; y < c->height; y++) { - for (x = 0; x < w2; x++) - { - video_st.frame->data[1][y * video_st.frame->linesize[1] + x] = inBuf[ofs]; ofs++; - } + memcpy( outBuf, &inBuf[ofs], inLineSize ); ofs += inLineSize; + + outBuf += ost->tmp_frame->linesize[0]; } - for (y = 0; y < h2; y++) - { - for (x = 0; x < w2; x++) - { - video_st.frame->data[2][y * video_st.frame->linesize[2] + x] = inBuf[ofs]; ofs++; - } - } - //memcpy( video_st.frame->data[0], &inBuf[ofs], luma_size ); ofs += luma_size; - //memcpy( video_st.frame->data[1], &inBuf[ofs], chroma_size ); ofs += chroma_size; - //memcpy( video_st.frame->data[2], &inBuf[ofs], chroma_size ); ofs += chroma_size; + + sws_scale(ost->sws_ctx, (const uint8_t * const *) ost->tmp_frame->data, + ost->tmp_frame->linesize, 0, c->height, ost->frame->data, + ost->frame->linesize); video_st.frame->pts++; - //printf("Write Frame: %li\n", video_st.frame->pts); - /* encode the image */ ret = avcodec_send_frame(c, video_st.frame); if (ret < 0) @@ -1179,6 +1165,9 @@ static int close(void) * av_codec_close(). */ av_write_trailer(oc); + video_st.close(); + audio_st.close(); + /* Close the output file. */ avio_close(oc->pb); @@ -1681,8 +1670,11 @@ void AviRecordDiskThread_t::run(void) #ifdef _USE_LIBAV else if ( localVideoFormat == AVI_LIBAV) { - Convert_4byte_To_I420Frame<4>(videoOut,rgb24,numPixels,width); - LIBAV::encode_video_frame( rgb24 ); + //Convert_4byte_To_I420Frame<4>(videoOut,rgb24,numPixels,width); + //convertRgb_32_to_24( (const unsigned char*)videoOut, rgb24, + // width, height, numPixels, true ); + //LIBAV::encode_video_frame( rgb24 ); + LIBAV::encode_video_frame( (unsigned char*)videoOut ); } #endif else From e4cd3b0d1a72eea14de4f1a9228c4196a8c7afd0 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Tue, 7 Sep 2021 20:59:59 -0400 Subject: [PATCH 04/19] Added libav audio encoding logic. --- src/drivers/Qt/AviRecord.cpp | 127 +++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 4 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index ec972fa1..a1612d73 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -714,6 +714,7 @@ struct OutputStream AVFrame *tmp_frame; struct SwsContext *sws_ctx; AVAudioResampleContext *avr; + int64_t next_pts; OutputStream(void) { @@ -722,6 +723,7 @@ struct OutputStream frame = tmp_frame = NULL; sws_ctx = NULL; avr = NULL; + next_pts = 0; } void close(void) @@ -742,6 +744,7 @@ struct OutputStream { avresample_free(&avr); avr = NULL; } + next_pts = 0; } }; static OutputStream video_st; @@ -776,6 +779,9 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) int ret; AVCodec *codec; AVCodecContext *c; + double fps; + + fps = getBaseFrameRate(); /* find the video encoder */ codec = avcodec_find_encoder(codec_id); @@ -814,12 +820,12 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) * of which frame timestamps are represented. For fixed-fps content, * timebase should be 1/framerate and timestamp increments should be * identical to 1. */ - ost->st->time_base = (AVRational){ 1, 60 }; + ost->st->time_base = (AVRational){ 1000, fps * 1000 }; c->time_base = ost->st->time_base; c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = AV_PIX_FMT_YUV420P; - printf("PIX_FMT:%i\n", c->pix_fmt ); + //printf("PIX_FMT:%i\n", c->pix_fmt ); if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) { @@ -1086,6 +1092,117 @@ static int init( int width, int height ) return 0; } +static int encode_audio_frame( int16_t *audioOut, int numSamples) +{ + int i,j, ret; + OutputStream *ost = &audio_st; + AVFrame *frame = NULL; + + if ( audioOut ) + { + frame = ost->tmp_frame; + + int16_t *q = (int16_t*)frame->data[0]; + + for (j = 0; j < numSamples; j++) + { + for (i = 0; i < ost->enc->channels; i++) + { + *q++ = audioOut[j]; + } + } + frame->nb_samples = numSamples; + + ret = avresample_convert(ost->avr, NULL, 0, 0, + frame->extended_data, frame->linesize[0], + frame->nb_samples); + if (ret < 0) + { + fprintf(stderr, "Error feeding audio data to the resampler\n"); + return -1; + } + } + + while ( ( frame && (avresample_available(ost->avr) >= ost->frame->nb_samples)) || + (!frame && (avresample_get_out_samples(ost->avr, 0) > 0) ) ) + { + /* when we pass a frame to the encoder, it may keep a reference to it + * internally; + * make sure we do not overwrite it here + */ + ret = av_frame_make_writable(ost->frame); + if (ret < 0) + { + return -1; + } + /* the difference between the two avresample calls here is that the + * first one just reads the already converted data that is buffered in + * the lavr output buffer, while the second one also flushes the + * resampler */ + if ( frame ) + { + ret = avresample_read(ost->avr, ost->frame->extended_data, + ost->frame->nb_samples); + } + else + { + ret = avresample_convert(ost->avr, ost->frame->extended_data, + ost->frame->linesize[0], ost->frame->nb_samples, + NULL, 0, 0); + } + if (ret < 0) + { + fprintf(stderr, "Error while resampling\n"); + return -1; + } + else if ( frame && (ret != ost->frame->nb_samples) ) + { + fprintf(stderr, "Too few samples returned from lavr\n"); + return -1; + } + ost->frame->nb_samples = ret; + ost->frame->pts = ost->next_pts; + ost->next_pts += ost->frame->nb_samples; + //got_output |= encode_audio_frame(oc, ost, ret ? ost->frame : NULL); + + if ( ret == 0 ) + { + fprintf(stderr, "Last Audio Frame\n"); + } + ret = avcodec_send_frame(ost->enc, (ret > 0) ? ost->frame : NULL); + if (ret < 0) + { + fprintf(stderr, "Error submitting a frame for encoding\n"); + return -1; + } + while (ret >= 0) + { + AVPacket pkt = { 0 }; // data and size must be 0; + av_init_packet(&pkt); + ret = avcodec_receive_packet(ost->enc, &pkt); + if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) + { + fprintf(stderr, "Error encoding a video frame\n"); + return -1; + } + else if (ret >= 0) + { + av_packet_rescale_ts(&pkt, ost->enc->time_base, ost->st->time_base); + pkt.stream_index = ost->st->index; + /* Write the compressed frame to the media file. */ + ret = av_interleaved_write_frame(oc, &pkt); + if (ret < 0) + { + fprintf(stderr, "Error while writing video frame\n"); + return -1; + } + } + } + } + + return 0; +} + static int encode_video_frame( unsigned char *inBuf ) { int ret, y, ofs, inLineSize; @@ -1117,7 +1234,7 @@ static int encode_video_frame( unsigned char *inBuf ) ost->tmp_frame->linesize, 0, c->height, ost->frame->data, ost->frame->linesize); - video_st.frame->pts++; + video_st.frame->pts = video_st.next_pts++; /* encode the image */ ret = avcodec_send_frame(c, video_st.frame); @@ -1159,6 +1276,8 @@ static int encode_video_frame( unsigned char *inBuf ) static int close(void) { + encode_audio_frame( NULL, 0 ); + /* Write the trailer, if any. The trailer must be written before you * close the CodecContexts open when you wrote the header; otherwise * av_write_trailer() may try to use memory that was freed on @@ -1708,7 +1827,7 @@ void AviRecordDiskThread_t::run(void) #ifdef _USE_LIBAV if ( localVideoFormat == AVI_LIBAV) { - + LIBAV::encode_audio_frame( audioOut, numSamples ); } else #endif From d8d7b59f3434d09675a6f3f93f5bec9e8b97d287 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Tue, 7 Sep 2021 22:02:43 -0400 Subject: [PATCH 05/19] Removed deprecated libavresample functions in favor of libswresample. --- src/CMakeLists.txt | 2 +- src/drivers/Qt/AviRecord.cpp | 185 +++++++++++++++++------------------ 2 files changed, 90 insertions(+), 97 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a0bc1f42..fb469ca5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -90,7 +90,7 @@ else(WIN32) add_definitions( -D_USE_X265 ${X265_CFLAGS} ) endif() - pkg_check_modules( LIBAV libavcodec libavformat libavutil libavresample libswresample libswscale) + pkg_check_modules( LIBAV libavcodec libavformat libavutil libswresample libswscale) if ( ${LIBAV_FOUND} ) message( STATUS "Using System Libav Library ${LIBAV_VERSION}" ) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index a1612d73..34d860ad 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -49,8 +49,9 @@ extern "C" { #include "libavutil/opt.h" #include "libavformat/avformat.h" -#include "libavresample/avresample.h" +//#include "libavresample/avresample.h" #include "libswscale/swscale.h" +#include "libswresample/swresample.h" } #endif #endif @@ -713,7 +714,7 @@ struct OutputStream AVFrame *frame; AVFrame *tmp_frame; struct SwsContext *sws_ctx; - AVAudioResampleContext *avr; + struct SwrContext *swr_ctx; int64_t next_pts; OutputStream(void) @@ -722,7 +723,7 @@ struct OutputStream enc = NULL; frame = tmp_frame = NULL; sws_ctx = NULL; - avr = NULL; + swr_ctx = NULL; next_pts = 0; } @@ -740,9 +741,9 @@ struct OutputStream { av_frame_free(&tmp_frame); tmp_frame = NULL; } - if ( avr != NULL ) + if ( swr_ctx != NULL ) { - avresample_free(&avr); avr = NULL; + swr_free(&swr_ctx); swr_ctx = NULL; } next_pts = 0; } @@ -780,9 +781,12 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) AVCodec *codec; AVCodecContext *c; double fps; + int fps1000; fps = getBaseFrameRate(); + fps1000 = (int)(fps * 1000.0); + /* find the video encoder */ codec = avcodec_find_encoder(codec_id); @@ -820,7 +824,7 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) * of which frame timestamps are represented. For fixed-fps content, * timebase should be 1/framerate and timestamp increments should be * identical to 1. */ - ost->st->time_base = (AVRational){ 1000, fps * 1000 }; + ost->st->time_base = (AVRational){ 1000, fps1000 }; c->time_base = ost->st->time_base; c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = AV_PIX_FMT_YUV420P; @@ -971,20 +975,20 @@ static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) * if the encoder supports the generated format directly -- the price is * some extra data copying; */ - ost->avr = avresample_alloc_context(); - if (!ost->avr) + ost->swr_ctx = swr_alloc(); + if (!ost->swr_ctx) { fprintf(stderr, "Error allocating the resampling context\n"); return -1; } - av_opt_set_int(ost->avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); - av_opt_set_int(ost->avr, "in_sample_rate", audioSampleRate, 0); - av_opt_set_int(ost->avr, "in_channel_layout", AV_CH_LAYOUT_MONO, 0); - av_opt_set_int(ost->avr, "out_sample_fmt", c->sample_fmt, 0); - av_opt_set_int(ost->avr, "out_sample_rate", c->sample_rate, 0); - av_opt_set_int(ost->avr, "out_channel_layout", c->channel_layout, 0); + av_opt_set_sample_fmt(ost->swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(ost->swr_ctx, "in_sample_rate", audioSampleRate, 0); + av_opt_set_int(ost->swr_ctx, "in_channel_layout", AV_CH_LAYOUT_MONO, 0); + av_opt_set_sample_fmt(ost->swr_ctx, "out_sample_fmt", c->sample_fmt, 0); + av_opt_set_int(ost->swr_ctx, "out_sample_rate", c->sample_rate, 0); + av_opt_set_int(ost->swr_ctx, "out_channel_layout", c->channel_layout, 0); - ret = avresample_open(ost->avr); + ret = swr_init(ost->swr_ctx); if (ret < 0) { fprintf(stderr, "Error opening the resampling context\n"); @@ -1025,7 +1029,7 @@ static int initMedia( const char *filename ) AVOutputFormat *fmt; /* Initialize libavcodec, and register all codecs and formats. */ - av_register_all(); + //av_register_all(); /* Autodetect the output format from the name. default is MPEG. */ fmt = av_guess_format(NULL, filename, NULL); @@ -1092,6 +1096,50 @@ static int init( int width, int height ) return 0; } +static int write_audio_frame( AVFrame *frame ) +{ + int ret; + OutputStream *ost = &audio_st; + + if ( frame ) + { + frame->pts = ost->next_pts; + ost->next_pts += frame->nb_samples; + } + + ret = avcodec_send_frame(ost->enc, frame); + + if (ret < 0) + { + fprintf(stderr, "Error submitting a frame for encoding\n"); + return -1; + } + while (ret >= 0) + { + AVPacket pkt = { 0 }; // data and size must be 0; + av_init_packet(&pkt); + ret = avcodec_receive_packet(ost->enc, &pkt); + if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) + { + fprintf(stderr, "Error encoding a video frame\n"); + return -1; + } + else if (ret >= 0) + { + av_packet_rescale_ts(&pkt, ost->enc->time_base, ost->st->time_base); + pkt.stream_index = ost->st->index; + /* Write the compressed frame to the media file. */ + ret = av_interleaved_write_frame(oc, &pkt); + if (ret < 0) + { + fprintf(stderr, "Error while writing video frame\n"); + return -1; + } + } + } + return 0; +} + static int encode_audio_frame( int16_t *audioOut, int numSamples) { int i,j, ret; @@ -1112,94 +1160,39 @@ static int encode_audio_frame( int16_t *audioOut, int numSamples) } } frame->nb_samples = numSamples; - - ret = avresample_convert(ost->avr, NULL, 0, 0, - frame->extended_data, frame->linesize[0], - frame->nb_samples); - if (ret < 0) - { - fprintf(stderr, "Error feeding audio data to the resampler\n"); - return -1; - } } - while ( ( frame && (avresample_available(ost->avr) >= ost->frame->nb_samples)) || - (!frame && (avresample_get_out_samples(ost->avr, 0) > 0) ) ) + ret = av_frame_make_writable(ost->frame); + + if (ret < 0) { - /* when we pass a frame to the encoder, it may keep a reference to it - * internally; - * make sure we do not overwrite it here - */ - ret = av_frame_make_writable(ost->frame); - if (ret < 0) + return -1; + } + ret = swr_convert_frame( ost->swr_ctx, ost->frame, audioOut ? ost->tmp_frame : NULL ); + //ret = avresample_convert(ost->swr_ctx, NULL, 0, 0, + // frame->extended_data, frame->linesize[0], + // frame->nb_samples); + if (ret < 0) + { + fprintf(stderr, "Error feeding audio data to the resampler\n"); + return -1; + } + if (ret == 0) + { + //printf("IN:%i OUT:%i\n", ost->tmp_frame->nb_samples, ost->frame->nb_samples ); + if ( write_audio_frame( ost->frame ) ) { - return -1; - } - /* the difference between the two avresample calls here is that the - * first one just reads the already converted data that is buffered in - * the lavr output buffer, while the second one also flushes the - * resampler */ - if ( frame ) - { - ret = avresample_read(ost->avr, ost->frame->extended_data, - ost->frame->nb_samples); - } - else - { - ret = avresample_convert(ost->avr, ost->frame->extended_data, - ost->frame->linesize[0], ost->frame->nb_samples, - NULL, 0, 0); - } - if (ret < 0) - { - fprintf(stderr, "Error while resampling\n"); return -1; } - else if ( frame && (ret != ost->frame->nb_samples) ) - { - fprintf(stderr, "Too few samples returned from lavr\n"); - return -1; - } - ost->frame->nb_samples = ret; - ost->frame->pts = ost->next_pts; - ost->next_pts += ost->frame->nb_samples; - //got_output |= encode_audio_frame(oc, ost, ret ? ost->frame : NULL); - - if ( ret == 0 ) - { - fprintf(stderr, "Last Audio Frame\n"); - } - ret = avcodec_send_frame(ost->enc, (ret > 0) ? ost->frame : NULL); - if (ret < 0) - { - fprintf(stderr, "Error submitting a frame for encoding\n"); - return -1; - } - while (ret >= 0) - { - AVPacket pkt = { 0 }; // data and size must be 0; - av_init_packet(&pkt); - ret = avcodec_receive_packet(ost->enc, &pkt); - if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) - { - fprintf(stderr, "Error encoding a video frame\n"); - return -1; - } - else if (ret >= 0) - { - av_packet_rescale_ts(&pkt, ost->enc->time_base, ost->st->time_base); - pkt.stream_index = ost->st->index; - /* Write the compressed frame to the media file. */ - ret = av_interleaved_write_frame(oc, &pkt); - if (ret < 0) - { - fprintf(stderr, "Error while writing video frame\n"); - return -1; - } - } - } } + if ( audioOut == NULL ) + { + if ( write_audio_frame( NULL ) ) + { + return -1; + } + } return 0; } From 425a2eedaaeda66bbd226d009da55636ed229cc5 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Tue, 7 Sep 2021 22:22:53 -0400 Subject: [PATCH 06/19] Added a print available libav encoders debug function. --- src/drivers/Qt/AviRecord.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 34d860ad..03b0f3d8 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -1024,6 +1024,31 @@ static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) return 0; } +static void print_Codecs(void) +{ + void *it = NULL; + const AVCodec *c; + + c = av_codec_iterate( &it ); + + while ( c != NULL ) + { + if ( av_codec_is_encoder(c) ) + { + if ( c->type == AVMEDIA_TYPE_VIDEO ) + { + printf("Video Encoder: %i %s %s\n", c->id, c->name, c->long_name); + } + else if ( c->type == AVMEDIA_TYPE_AUDIO ) + { + printf("Audio Encoder: %i %s %s\n", c->id, c->name, c->long_name); + } + } + + c = av_codec_iterate( &it ); + } +} + static int initMedia( const char *filename ) { AVOutputFormat *fmt; @@ -1031,6 +1056,8 @@ static int initMedia( const char *filename ) /* Initialize libavcodec, and register all codecs and formats. */ //av_register_all(); + print_Codecs(); + /* Autodetect the output format from the name. default is MPEG. */ fmt = av_guess_format(NULL, filename, NULL); From 679813e2d2515fb92718831c33e799b5076963f1 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Wed, 8 Sep 2021 23:14:17 -0400 Subject: [PATCH 07/19] libav option debugging in work. --- src/drivers/Qt/AviRecord.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 03b0f3d8..cc7731e6 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -814,6 +814,8 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) } ost->enc = c; + av_opt_show2( (void*)c, NULL, AV_OPT_FLAG_VIDEO_PARAM, 0 ); + /* Put sample parameters. */ c->bit_rate = 400000; /* Resolution must be a multiple of two. */ @@ -829,6 +831,26 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = AV_PIX_FMT_YUV420P; + if ( codec->pix_fmts ) + { + int i=0, formatOk=0; + while (codec->pix_fmts[i] != -1) + { + printf("Codec PIX_FMT: %i\n", codec->pix_fmts[i]); + if ( codec->pix_fmts[i] == c->pix_fmt ) + { + printf("CODEC Supports PIX_FMT:%i\n", c->pix_fmt ); + formatOk = 1; + } + i++; + } + if ( !formatOk ) + { + printf("CODEC Does Not Support PIX_FMT:%i Changing to:%i\n", c->pix_fmt, codec->pix_fmts[0] ); + c->pix_fmt = codec->pix_fmts[0]; + } + } + //printf("PIX_FMT:%i\n", c->pix_fmt ); if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) @@ -1085,6 +1107,8 @@ static int initMedia( const char *filename ) if ( initVideoStream( fmt->video_codec, &video_st ) ) //if ( initVideoStream( AV_CODEC_ID_H264, &video_st ) ) + //if ( initVideoStream( AV_CODEC_ID_FFV1, &video_st ) ) + //if ( initVideoStream( AV_CODEC_ID_RAWVIDEO, &video_st ) ) { return -1; } From a83826c187d4589568f33a38f5cd4c64f32be700 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Thu, 9 Sep 2021 22:14:09 -0400 Subject: [PATCH 08/19] Raw video BGR24 testing changes. --- src/drivers/Qt/AviRecord.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index cc7731e6..2e74df90 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -764,6 +764,8 @@ static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height) picture->width = width; picture->height = height; picture->pts = 0; + //picture->sample_aspect_ratio = (AVRational){ 4, 3 }; + /* allocate the buffers for the frame data */ ret = av_frame_get_buffer(picture, 0); if (ret < 0) @@ -830,6 +832,11 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) c->time_base = ost->st->time_base; c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = AV_PIX_FMT_YUV420P; + //c->pix_fmt = AV_PIX_FMT_BGR24; + + //c->sample_aspect_ratio = (AVRational){ 4, 3 }; + //printf("compression_level:%i\n", c->compression_level); + //printf("TAG:0x%08X\n", c->codec_tag); if ( codec->pix_fmts ) { @@ -1108,6 +1115,7 @@ static int initMedia( const char *filename ) if ( initVideoStream( fmt->video_codec, &video_st ) ) //if ( initVideoStream( AV_CODEC_ID_H264, &video_st ) ) //if ( initVideoStream( AV_CODEC_ID_FFV1, &video_st ) ) + //if ( initVideoStream( AV_CODEC_ID_RV20, &video_st ) ) //if ( initVideoStream( AV_CODEC_ID_RAWVIDEO, &video_st ) ) { return -1; From 233d555ffe3c28b33301e9b97a35daa5ec22f790 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Mon, 13 Sep 2021 23:04:14 -0400 Subject: [PATCH 09/19] Unsuccessful attempts at changing RGB->YUV coefficients to get proper color output. Turns out a gamma correction is needed that is more involved than I thought. --- src/drivers/Qt/AviRecord.cpp | 97 +++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 2e74df90..b70a8e09 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -122,19 +122,61 @@ static void convertRgb_32_to_24( const unsigned char *src, unsigned char *dest, //************************************************************************************** /* For RGB2YUV: */ -static const int RGB2YUV_SHIFT = 15; /* highest value where [RGB][YUV] fit in signed short */ +//static const int RGB2YUV_SHIFT = 15; // highest value where [RGB][YUV] fit in signed short -static const int RY = 8414; // ((int)(( 65.738/256.0)*(1<> RGB2YUV_SHIFT); // y + + dest[destpos[n]] = Y; } - dest[upos++] = (U_ADD + ((RU * c[0] + GU * c[1] + BU * c[2]) >> (RGB2YUV_SHIFT+2)) ); - dest[vpos++] = (V_ADD + ((RV * c[0] + GV * c[1] + BV * c[2]) >> (RGB2YUV_SHIFT+2)) ); + U = (U_ADD + ((RU * c[0] + GU * c[1] + BU * c[2]) >> (RGB2YUV_SHIFT+2)) ); + V = (V_ADD + ((RV * c[0] + GV * c[1] + BV * c[2]) >> (RGB2YUV_SHIFT+2)) ); + + //printf("%i,%i = rgb( %i, %i, %i ) = yuv( %i, %i, %i )\n", x,y, + // rgb[0][3], rgb[1][3], rgb[2][3], Y, U, V ); + dest[upos++] = U; + dest[vpos++] = V; } ypos += 2; @@ -212,10 +245,6 @@ void Convert_4byte_To_I420Frame(const void* data, unsigned char* dest, unsigned /*fprintf(stderr, ",yr=%u,ur=%u,vr=%u\n", ypos,upos,vpos);*/ - - //#ifdef __MMX__ - // MMX_clear(); - //#endif } //************************************************************************************** #ifdef _USE_X264 From 2007d03eef8b7db50e96a6e733bf7fca745e97fa Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Tue, 14 Sep 2021 00:56:59 -0400 Subject: [PATCH 10/19] Build fixes for linking libav in windows. --- src/CMakeLists.txt | 7 +++++++ src/drivers/Qt/AviRecord.cpp | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fb469ca5..6eb8b547 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,6 +36,13 @@ if(WIN32) set( SDL2_LDFLAGS ${SDL_INSTALL_PREFIX}/SDL2/lib/x64/SDL2.lib ) set( SYS_LIBS wsock32 ws2_32 vfw32 Htmlhelp ) set(APP_ICON_RESOURCES_WINDOWS ${CMAKE_SOURCE_DIR}/icons/fceux.rc ) + add_definitions( -D_USE_LIBAV ${LIBAV_CFLAGS} ) + include_directories( ${FFMPEG_INSTALL_PREFIX}/FFmpeg ) + set( LIBAV_LDFLAGS ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libavcodec/avcodec.lib + ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libavformat/avformat.lib + ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libavutil/avutil.lib + ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libswscale/swscale.lib + ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libswresample/swresample.lib ) else(WIN32) # Non Windows System # UNIX (Linux or Mac OSX) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index b70a8e09..accc190f 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -49,6 +49,7 @@ extern "C" { #include "libavutil/opt.h" #include "libavformat/avformat.h" +#include "libavcodec/avcodec.h" //#include "libavresample/avresample.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" @@ -809,7 +810,7 @@ static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height) static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) { int ret; - AVCodec *codec; + const AVCodec *codec; AVCodecContext *c; double fps; int fps1000; @@ -857,7 +858,9 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) * of which frame timestamps are represented. For fixed-fps content, * timebase should be 1/framerate and timestamp increments should be * identical to 1. */ - ost->st->time_base = (AVRational){ 1000, fps1000 }; + //ost->st->time_base = (AVRational){ 1000, fps1000 }; + ost->st->time_base.num = 1000; + ost->st->time_base.den = fps1000; c->time_base = ost->st->time_base; c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = AV_PIX_FMT_YUV420P; @@ -987,7 +990,7 @@ static AVFrame *alloc_audio_frame(enum AVSampleFormat sample_fmt, static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) { int ret, nb_samples; - AVCodec *codec; + const AVCodec *codec; AVCodecContext *c; /* find the audio encoder */ @@ -1022,7 +1025,9 @@ static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) c->channel_layout = codec->channel_layouts ? codec->channel_layouts[0] : AV_CH_LAYOUT_STEREO; c->channels = av_get_channel_layout_nb_channels(c->channel_layout); c->bit_rate = 64000; - ost->st->time_base = (AVRational){ 1, c->sample_rate }; + //ost->st->time_base = (AVRational){ 1, c->sample_rate }; + ost->st->time_base.num = 1; + ost->st->time_base.den = c->sample_rate; // some formats want stream headers to be separate if (oc->oformat->flags & AVFMT_GLOBALHEADER) { @@ -1109,7 +1114,7 @@ static void print_Codecs(void) static int initMedia( const char *filename ) { - AVOutputFormat *fmt; + const AVOutputFormat *fmt; /* Initialize libavcodec, and register all codecs and formats. */ //av_register_all(); @@ -1248,6 +1253,8 @@ static int encode_audio_frame( int16_t *audioOut, int numSamples) } } frame->nb_samples = numSamples; + + printf("numSamples: %i\n", numSamples); } ret = av_frame_make_writable(ost->frame); From 18768ef4b300d7ae0bb8ddc1d8ebaaa9ba156c41 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Tue, 14 Sep 2021 21:31:40 -0400 Subject: [PATCH 11/19] Bug fix for libav audio encoding. --- src/drivers/Qt/AviRecord.cpp | 120 +++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 28 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index accc190f..74c92794 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -746,6 +746,7 @@ struct OutputStream struct SwsContext *sws_ctx; struct SwrContext *swr_ctx; int64_t next_pts; + int bytesPerSample; OutputStream(void) { @@ -754,6 +755,7 @@ struct OutputStream frame = tmp_frame = NULL; sws_ctx = NULL; swr_ctx = NULL; + bytesPerSample = 0; next_pts = 0; } @@ -775,6 +777,7 @@ struct OutputStream { swr_free(&swr_ctx); swr_ctx = NULL; } + bytesPerSample = 0; next_pts = 0; } }; @@ -1074,8 +1077,14 @@ static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) nb_samples = c->frame_size; } + ost->bytesPerSample = av_get_bytes_per_sample( c->sample_fmt ); + ost->frame = alloc_audio_frame(c->sample_fmt, c->channel_layout, c->sample_rate, nb_samples); - ost->tmp_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, AV_CH_LAYOUT_MONO, audioSampleRate, nb_samples); + ost->tmp_frame = alloc_audio_frame(c->sample_fmt, c->channel_layout, c->sample_rate, nb_samples); + //ost->tmp_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, AV_CH_LAYOUT_MONO, audioSampleRate, nb_samples); + + printf("Audio: FMT:%i ChanLayout:%li Rate:%i FrameSize:%i bytesPerSample:%i \n", + c->sample_fmt, c->channel_layout, c->sample_rate, nb_samples, ost->bytesPerSample ); /* copy the stream parameters to the muxer */ ret = avcodec_parameters_from_context(ost->st->codecpar, c); @@ -1084,6 +1093,8 @@ static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) fprintf(stderr, "Could not copy the stream parameters\n"); return -1; } + ost->frame->nb_samples = 0; + return 0; } @@ -1114,7 +1125,8 @@ static void print_Codecs(void) static int initMedia( const char *filename ) { - const AVOutputFormat *fmt; + //const AVOutputFormat *fmt; + AVOutputFormat *fmt; /* Initialize libavcodec, and register all codecs and formats. */ //av_register_all(); @@ -1199,6 +1211,7 @@ static int write_audio_frame( AVFrame *frame ) frame->pts = ost->next_pts; ost->next_pts += frame->nb_samples; } + //printf("Encoding Audio: %i\n", frame->nb_samples ); ret = avcodec_send_frame(ost->enc, frame); @@ -1235,54 +1248,105 @@ static int write_audio_frame( AVFrame *frame ) static int encode_audio_frame( int16_t *audioOut, int numSamples) { - int i,j, ret; + int i,ret; OutputStream *ost = &audio_st; - AVFrame *frame = NULL; + const uint8_t *inData[AV_NUM_DATA_POINTERS]; - if ( audioOut ) + for (i=0; itmp_frame; - - int16_t *q = (int16_t*)frame->data[0]; - - for (j = 0; j < numSamples; j++) - { - for (i = 0; i < ost->enc->channels; i++) - { - *q++ = audioOut[j]; - } - } - frame->nb_samples = numSamples; - - printf("numSamples: %i\n", numSamples); + inData[i] = 0; } - ret = av_frame_make_writable(ost->frame); + if ( audioOut ) + { + inData[0] = (const uint8_t*)audioOut; + + //printf("Audio NumSamplesIn: %i\n", numSamples); + } + + ret = av_frame_make_writable(ost->tmp_frame); if (ret < 0) { return -1; } - ret = swr_convert_frame( ost->swr_ctx, ost->frame, audioOut ? ost->tmp_frame : NULL ); - //ret = avresample_convert(ost->swr_ctx, NULL, 0, 0, - // frame->extended_data, frame->linesize[0], - // frame->nb_samples); + + if ( audioOut ) + { + ret = swr_convert( ost->swr_ctx, ost->tmp_frame->data, ost->tmp_frame->linesize[0], inData, numSamples ); + } + else + { + ret = swr_convert( ost->swr_ctx, ost->tmp_frame->data, ost->tmp_frame->linesize[0], NULL, 0 ); + } + if (ret < 0) { fprintf(stderr, "Error feeding audio data to the resampler\n"); return -1; } - if (ret == 0) + if (ret > 0) { - //printf("IN:%i OUT:%i\n", ost->tmp_frame->nb_samples, ost->frame->nb_samples ); - if ( write_audio_frame( ost->frame ) ) + int spaceAvail, samplesLeft, copySize, srcOffset = 0; + + samplesLeft = ost->tmp_frame->nb_samples = ret; + + spaceAvail = ost->enc->frame_size - ost->frame->nb_samples; + + while ( samplesLeft > 0 ) { - return -1; + if ( spaceAvail >= samplesLeft ) + { + copySize = samplesLeft; + } + else + { + copySize = spaceAvail; + } + //printf("Audio: Space:%i Data:%i Copy:%i\n", spaceAvail, samplesLeft, copySize ); + + ret = av_frame_make_writable(ost->frame); + + if (ret < 0) + { + fprintf(stderr, "Error audio av_frame_make_writable\n"); + return -1; + } + + ret = av_samples_copy( ost->frame->data, ost->tmp_frame->data, + ost->frame->nb_samples, srcOffset, copySize, + ost->frame->channels, ost->enc->sample_fmt ); + + if ( ret < 0 ) + { + return -1; + } + ost->frame->nb_samples += copySize; + srcOffset += copySize; + samplesLeft -= copySize; + + if ( ost->frame->nb_samples >= ost->enc->frame_size ) + { + if ( write_audio_frame( ost->frame ) ) + { + return -1; + } + ost->frame->nb_samples = 0; + } + spaceAvail = ost->enc->frame_size - ost->frame->nb_samples; } } if ( audioOut == NULL ) { + if ( ost->frame->nb_samples > 0 ) + { + if ( write_audio_frame( ost->frame ) ) + { + return -1; + } + ost->frame->nb_samples = 0; + } if ( write_audio_frame( NULL ) ) { return -1; From 8e2af98295c4d453a2c33afab70bb07b41ad5688 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Tue, 14 Sep 2021 21:36:50 -0400 Subject: [PATCH 12/19] Build fix for various versions of libav. --- src/drivers/Qt/AviRecord.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 74c92794..3f377cd0 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -1125,8 +1125,11 @@ static void print_Codecs(void) static int initMedia( const char *filename ) { - //const AVOutputFormat *fmt; +#ifdef ff_const59 + ff_const59 AVOutputFormat *fmt; +#else AVOutputFormat *fmt; +#endif /* Initialize libavcodec, and register all codecs and formats. */ //av_register_all(); From 954e7d847c56b682c0417e4181b56c5c803d8801 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Wed, 15 Sep 2021 01:54:51 -0400 Subject: [PATCH 13/19] Windows build fix for compiling against newer libav. --- src/drivers/Qt/AviRecord.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 3f377cd0..5321be32 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -1125,12 +1125,11 @@ static void print_Codecs(void) static int initMedia( const char *filename ) { -#ifdef ff_const59 - ff_const59 AVOutputFormat *fmt; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT( 59, 0, 0 ) + const AVOutputFormat *fmt; #else AVOutputFormat *fmt; #endif - /* Initialize libavcodec, and register all codecs and formats. */ //av_register_all(); From 3262e182e86daadcac3ecc67cbeff377f43de259 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Thu, 16 Sep 2021 21:20:59 -0400 Subject: [PATCH 14/19] Setup qt win64 pipeline to auto download libav from external site and link into program. Make linking libav optional in cmake for qt win64 build. --- pipelines/qwin64_build.bat | 9 ++++++++- src/CMakeLists.txt | 18 +++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/pipelines/qwin64_build.bat b/pipelines/qwin64_build.bat index 763e775f..4b197c5a 100644 --- a/pipelines/qwin64_build.bat +++ b/pipelines/qwin64_build.bat @@ -22,18 +22,24 @@ cd build mkdir bin curl -s -LO http://www.libsdl.org/release/SDL2-devel-2.0.14-VC.zip +curl -s -LO https://github.com/GyanD/codexffmpeg/releases/download/4.4/ffmpeg-4.4-full_build-shared.zip REM rmdir /q /s SDL2 powershell -command "Expand-Archive" SDL2-devel-2.0.14-VC.zip . +powershell -command "Expand-Archive" ffmpeg-4.4-full_build-shared.zip rename SDL2-2.0.14 SDL2 +move ffmpeg-4.4-full_build-shared\ffmpeg-4.4-full_build-shared ffmpeg +rmdir ffmpeg-4.4-full_build-shared +del ffmpeg-4.4-full_build-shared.zip set SDL_INSTALL_PREFIX=%CD% +set FFMPEG_INSTALL_PREFIX=%CD% REM cmake -h REM cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DSDL_INSTALL_PREFIX=%SDL_INSTALL_PREFIX% .. -cmake -DQT6=0 -DSDL_INSTALL_PREFIX=%SDL_INSTALL_PREFIX% .. +cmake -DQT6=0 -DSDL_INSTALL_PREFIX=%SDL_INSTALL_PREFIX% -DUSE_LIBAV=1 -DFFMPEG_INSTALL_PREFIX=%FFMPEG_INSTALL_PREFIX% .. REM nmake msbuild /m fceux.sln /p:Configuration=Release @@ -41,6 +47,7 @@ msbuild /m fceux.sln /p:Configuration=Release copy src\Release\fceux.exe bin\qfceux.exe copy %SDL_INSTALL_PREFIX%\SDL2\lib\x64\SDL2.dll bin\. +copy %FFMPEG_INSTALL_PREFIX%\ffmpeg\bin\*.dll bin\. windeployqt --no-compiler-runtime bin\qfceux.exe diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6eb8b547..1b509c40 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,13 +36,17 @@ if(WIN32) set( SDL2_LDFLAGS ${SDL_INSTALL_PREFIX}/SDL2/lib/x64/SDL2.lib ) set( SYS_LIBS wsock32 ws2_32 vfw32 Htmlhelp ) set(APP_ICON_RESOURCES_WINDOWS ${CMAKE_SOURCE_DIR}/icons/fceux.rc ) - add_definitions( -D_USE_LIBAV ${LIBAV_CFLAGS} ) - include_directories( ${FFMPEG_INSTALL_PREFIX}/FFmpeg ) - set( LIBAV_LDFLAGS ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libavcodec/avcodec.lib - ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libavformat/avformat.lib - ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libavutil/avutil.lib - ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libswscale/swscale.lib - ${FFMPEG_INSTALL_PREFIX}/FFmpeg/libswresample/swresample.lib ) + + if ( ${USE_LIBAV} ) + add_definitions( -D_USE_LIBAV ${LIBAV_CFLAGS} ) + include_directories( ${FFMPEG_INSTALL_PREFIX}/ffmpeg/include ) + set( LIBAV_LDFLAGS ${FFMPEG_INSTALL_PREFIX}/ffmpeg/lib/avcodec.lib + ${FFMPEG_INSTALL_PREFIX}/ffmpeg/lib/avformat.lib + ${FFMPEG_INSTALL_PREFIX}/ffmpeg/lib/avutil.lib + ${FFMPEG_INSTALL_PREFIX}/ffmpeg/lib/swscale.lib + ${FFMPEG_INSTALL_PREFIX}/ffmpeg/lib/swresample.lib ) + endif() + else(WIN32) # Non Windows System # UNIX (Linux or Mac OSX) From b514c143b695893c513e7a10e2c63bfe6bbd1e28 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Thu, 16 Sep 2021 23:15:32 -0400 Subject: [PATCH 15/19] libav movie options in work. --- src/drivers/Qt/AviRecord.cpp | 124 ++++++++++++++++++++++++++++++-- src/drivers/Qt/AviRecord.h | 25 +++++++ src/drivers/Qt/MovieOptions.cpp | 72 +++++++++++++++---- src/drivers/Qt/MovieOptions.h | 4 ++ 4 files changed, 207 insertions(+), 18 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 5321be32..9a661862 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -747,6 +747,8 @@ struct OutputStream struct SwrContext *swr_ctx; int64_t next_pts; int bytesPerSample; + int frameSize; + AVCodecID selEnc; OutputStream(void) { @@ -756,7 +758,9 @@ struct OutputStream sws_ctx = NULL; swr_ctx = NULL; bytesPerSample = 0; + frameSize = 0; next_pts = 0; + selEnc = AV_CODEC_ID_NONE; } void close(void) @@ -1077,6 +1081,7 @@ static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) nb_samples = c->frame_size; } + ost->frameSize = nb_samples; ost->bytesPerSample = av_get_bytes_per_sample( c->sample_fmt ); ost->frame = alloc_audio_frame(c->sample_fmt, c->channel_layout, c->sample_rate, nb_samples); @@ -1160,16 +1165,27 @@ static int initMedia( const char *filename ) //strncpy(oc->filename, filename, sizeof(oc->filename)); - if ( initVideoStream( fmt->video_codec, &video_st ) ) + if ( video_st.selEnc == AV_CODEC_ID_NONE ) + { + video_st.selEnc = fmt->video_codec; + } + if ( audio_st.selEnc == AV_CODEC_ID_NONE ) + { + audio_st.selEnc = fmt->audio_codec; + } + + if ( initVideoStream( video_st.selEnc, &video_st ) ) //if ( initVideoStream( AV_CODEC_ID_H264, &video_st ) ) //if ( initVideoStream( AV_CODEC_ID_FFV1, &video_st ) ) //if ( initVideoStream( AV_CODEC_ID_RV20, &video_st ) ) //if ( initVideoStream( AV_CODEC_ID_RAWVIDEO, &video_st ) ) { + printf("Video Stream Init Failed\n"); return -1; } - if ( initAudioStream( fmt->audio_codec, &audio_st ) ) + if ( initAudioStream( audio_st.selEnc, &audio_st ) ) { + printf("Audio Stream Init Failed\n"); return -1; } @@ -1192,6 +1208,7 @@ static int initMedia( const char *filename ) /* Write the stream header, if any. */ if ( avformat_write_header(oc, NULL) ) { + printf("Error: Failed to write avformat header\n"); return -1; } @@ -1289,11 +1306,13 @@ static int encode_audio_frame( int16_t *audioOut, int numSamples) } if (ret > 0) { - int spaceAvail, samplesLeft, copySize, srcOffset = 0; + int spaceAvail, samplesLeft, copySize, srcOffset = 0, frameSize; + + frameSize = ost->frameSize; samplesLeft = ost->tmp_frame->nb_samples = ret; - spaceAvail = ost->enc->frame_size - ost->frame->nb_samples; + spaceAvail = frameSize - ost->frame->nb_samples; while ( samplesLeft > 0 ) { @@ -1327,7 +1346,7 @@ static int encode_audio_frame( int16_t *audioOut, int numSamples) srcOffset += copySize; samplesLeft -= copySize; - if ( ost->frame->nb_samples >= ost->enc->frame_size ) + if ( ost->frame->nb_samples >= frameSize ) { if ( write_audio_frame( ost->frame ) ) { @@ -1335,7 +1354,7 @@ static int encode_audio_frame( int16_t *audioOut, int numSamples) } ost->frame->nb_samples = 0; } - spaceAvail = ost->enc->frame_size - ost->frame->nb_samples; + spaceAvail = frameSize - ost->frame->nb_samples; } } @@ -2035,3 +2054,96 @@ void AviRecordDiskThread_t::run(void) } //---------------------------------------------------- //************************************************************************************** +//************************************************************************************** +//***************************** Options Pages ***************************************** +//************************************************************************************** +//************************************************************************************** +#ifdef _USE_LIBAV +LibavOptionsPage::LibavOptionsPage(QWidget *parent) + : QWidget(parent) +{ + QLabel *lbl; + QVBoxLayout *vbox1; + QHBoxLayout *hbox; + + vbox1 = new QVBoxLayout(); + + videoEncSel = new QComboBox(); + audioEncSel = new QComboBox(); + + hbox = new QHBoxLayout(); + vbox1->addLayout(hbox); + lbl = new QLabel( tr("Video Encoder:") ); + hbox->addWidget( lbl ); + hbox->addWidget( videoEncSel ); + + hbox = new QHBoxLayout(); + vbox1->addLayout(hbox); + lbl = new QLabel( tr("Audio Encoder:") ); + hbox->addWidget( lbl ); + hbox->addWidget( audioEncSel ); + + initCodecLists(); + + setLayout(vbox1); + + connect(videoEncSel, SIGNAL(currentIndexChanged(int)), this, SLOT(videoCodecChanged(int))); + connect(audioEncSel, SIGNAL(currentIndexChanged(int)), this, SLOT(audioCodecChanged(int))); +} +//----------------------------------------------------- +LibavOptionsPage::~LibavOptionsPage(void) +{ + +} +//----------------------------------------------------- +void LibavOptionsPage::initCodecLists(void) +{ + void *it = NULL; + const AVCodec *c; + const AVOutputFormat *ofmt; + int compatible; + + ofmt = av_guess_format("avi", NULL, NULL); + + c = av_codec_iterate( &it ); + + while ( c != NULL ) + { + if ( av_codec_is_encoder(c) ) + { + if ( c->type == AVMEDIA_TYPE_VIDEO ) + { + compatible = avformat_query_codec( ofmt, c->id, FF_COMPLIANCE_NORMAL ); + printf("Video Encoder: %i %s %s\t:%i\n", c->id, c->name, c->long_name, compatible); + if ( compatible ) + { + videoEncSel->addItem( tr(c->name), c->id ); + } + } + else if ( c->type == AVMEDIA_TYPE_AUDIO ) + { + compatible = avformat_query_codec( ofmt, c->id, FF_COMPLIANCE_NORMAL ); + printf("Audio Encoder: %i %s %s\t:%i\n", c->id, c->name, c->long_name, compatible); + if ( compatible ) + { + audioEncSel->addItem( tr(c->name), c->id ); + } + } + } + + c = av_codec_iterate( &it ); + } +} +//----------------------------------------------------- +void LibavOptionsPage::videoCodecChanged(int idx) +{ + LIBAV::video_st.selEnc = (AVCodecID)videoEncSel->itemData(idx).toInt(); +} +//----------------------------------------------------- +void LibavOptionsPage::audioCodecChanged(int idx) +{ + LIBAV::audio_st.selEnc = (AVCodecID)audioEncSel->itemData(idx).toInt(); +} +//----------------------------------------------------- +#endif +//************************************************************************************** diff --git a/src/drivers/Qt/AviRecord.h b/src/drivers/Qt/AviRecord.h index 11d68cb1..79504a2f 100644 --- a/src/drivers/Qt/AviRecord.h +++ b/src/drivers/Qt/AviRecord.h @@ -10,6 +10,8 @@ #include #include +#include +#include enum aviEncoderList { @@ -68,4 +70,27 @@ class AviRecordDiskThread_t : public QThread signals: void finished(void); }; + +#ifdef _USE_LIBAV +class LibavOptionsPage : public QWidget +{ + Q_OBJECT + + public: + LibavOptionsPage(QWidget *parent = nullptr); + ~LibavOptionsPage(void); + + protected: + QComboBox *videoEncSel; + QComboBox *audioEncSel; + + void initCodecLists(void); + + private slots: + void videoCodecChanged(int idx); + void audioCodecChanged(int idx); +}; + +#endif + #endif diff --git a/src/drivers/Qt/MovieOptions.cpp b/src/drivers/Qt/MovieOptions.cpp index c90b670f..36a2e3f2 100644 --- a/src/drivers/Qt/MovieOptions.cpp +++ b/src/drivers/Qt/MovieOptions.cpp @@ -35,6 +35,7 @@ #include "Qt/input.h" #include "Qt/config.h" #include "Qt/keyscan.h" +#include "Qt/AviRecord.h" #include "Qt/fceuWrapper.h" #include "Qt/MovieOptions.h" @@ -43,13 +44,20 @@ MovieOptionsDialog_t::MovieOptionsDialog_t(QWidget *parent) : QDialog(parent) { QLabel *lbl; - QVBoxLayout *mainLayout; + QGroupBox *gbox; + QHBoxLayout *mainLayout; QHBoxLayout *hbox; + QVBoxLayout *vbox1, *vbox2; QPushButton *closeButton; + std::vector aviDriverList; setWindowTitle("Movie Options"); - mainLayout = new QVBoxLayout(); + mainLayout = new QHBoxLayout(); + vbox1 = new QVBoxLayout(); + vbox2 = new QVBoxLayout(); + + mainLayout->addLayout(vbox1); readOnlyReplay = new QCheckBox(tr("Always Suggest Read-Only Replay")); pauseAfterPlay = new QCheckBox(tr("Pause After Playback")); @@ -63,15 +71,15 @@ MovieOptionsDialog_t::MovieOptionsDialog_t(QWidget *parent) lbl = new QLabel(tr("Loading states in record mode will not immediately truncate movie, next frame input will. (VBA-rr and SNES9x style)")); lbl->setWordWrap(true); - mainLayout->addWidget(readOnlyReplay); - mainLayout->addWidget(pauseAfterPlay); - mainLayout->addWidget(closeAfterPlay); - mainLayout->addWidget(bindSaveStates); - mainLayout->addWidget(dpySubTitles); - mainLayout->addWidget(putSubTitlesAvi); - mainLayout->addWidget(autoBackUp); - mainLayout->addWidget(loadFullStates); - mainLayout->addWidget(lbl); + vbox1->addWidget(readOnlyReplay); + vbox1->addWidget(pauseAfterPlay); + vbox1->addWidget(closeAfterPlay); + vbox1->addWidget(bindSaveStates); + vbox1->addWidget(dpySubTitles); + vbox1->addWidget(putSubTitlesAvi); + vbox1->addWidget(autoBackUp); + vbox1->addWidget(loadFullStates); + vbox1->addWidget(lbl); readOnlyReplay->setChecked(suggestReadOnlyReplay); pauseAfterPlay->setChecked(pauseAfterPlayback); @@ -89,8 +97,40 @@ MovieOptionsDialog_t::MovieOptionsDialog_t(QWidget *parent) hbox = new QHBoxLayout(); hbox->addStretch(5); hbox->addWidget( closeButton, 1 ); - mainLayout->addLayout( hbox ); + vbox1->addLayout( hbox ); + FCEUD_AviGetFormatOpts( aviDriverList ); + + gbox = new QGroupBox( tr("AVI Recording Options") ); + gbox->setLayout(vbox2); + mainLayout->addWidget(gbox); + hbox = new QHBoxLayout(); + aviBackend = new QComboBox(); + aviPageStack = new QStackedWidget(); + lbl = new QLabel(tr("AVI Backend Driver:")); + hbox->addWidget( lbl ); + hbox->addWidget( aviBackend ); + vbox2->addLayout( hbox ); + vbox2->addWidget( aviPageStack ); + + for (size_t i=0; iaddItem(tr(aviDriverList[i].c_str()), (unsigned int)i); + + switch (i) + { + #ifdef _USE_LIBAV + case AVI_LIBAV: + { + aviPageStack->addWidget( new LibavOptionsPage() ); + } + break; + #endif + default: + aviPageStack->addWidget( new QWidget() ); + break; + } + } setLayout(mainLayout); connect(readOnlyReplay, SIGNAL(stateChanged(int)), this, SLOT(readOnlyReplayChanged(int))); @@ -101,6 +141,9 @@ MovieOptionsDialog_t::MovieOptionsDialog_t(QWidget *parent) connect(putSubTitlesAvi, SIGNAL(stateChanged(int)), this, SLOT(putSubTitlesAviChanged(int))); connect(autoBackUp, SIGNAL(stateChanged(int)), this, SLOT(autoBackUpChanged(int))); connect(loadFullStates, SIGNAL(stateChanged(int)), this, SLOT(loadFullStatesChanged(int))); + + connect(aviBackend, SIGNAL(currentIndexChanged(int)), this, SLOT(aviBackendChanged(int))); + } //---------------------------------------------------------------------------- MovieOptionsDialog_t::~MovieOptionsDialog_t(void) @@ -165,3 +208,8 @@ void MovieOptionsDialog_t::loadFullStatesChanged(int state) fullSaveStateLoads = (state != Qt::Unchecked); } //---------------------------------------------------------------------------- +void MovieOptionsDialog_t::aviBackendChanged(int idx) +{ + aviPageStack->setCurrentIndex(idx); +} +//---------------------------------------------------------------------------- diff --git a/src/drivers/Qt/MovieOptions.h b/src/drivers/Qt/MovieOptions.h index ea4dcdd8..65666c78 100644 --- a/src/drivers/Qt/MovieOptions.h +++ b/src/drivers/Qt/MovieOptions.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "Qt/main.h" @@ -37,6 +38,8 @@ protected: QCheckBox *putSubTitlesAvi; QCheckBox *autoBackUp; QCheckBox *loadFullStates; + QComboBox *aviBackend; + QStackedWidget *aviPageStack; private: public slots: @@ -50,4 +53,5 @@ private slots: void putSubTitlesAviChanged(int state); void autoBackUpChanged(int state); void loadFullStatesChanged(int state); + void aviBackendChanged(int idx); }; From 1dd9036da60c4ef578b6fb928ae7231bc2a82361 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Thu, 16 Sep 2021 23:40:29 -0400 Subject: [PATCH 16/19] Bug fix for libav MPEG4 video frame rate encoding. --- src/drivers/Qt/AviRecord.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 9a661862..cbb9c961 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -821,9 +821,12 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) AVCodecContext *c; double fps; int fps1000; + unsigned int usec; fps = getBaseFrameRate(); + usec = (unsigned int)((1.0e6 / fps)+0.50); + fps1000 = (int)(fps * 1000.0); /* find the video encoder */ @@ -866,8 +869,16 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) * timebase should be 1/framerate and timestamp increments should be * identical to 1. */ //ost->st->time_base = (AVRational){ 1000, fps1000 }; - ost->st->time_base.num = 1000; - ost->st->time_base.den = fps1000; + if ( codec_id == AV_CODEC_ID_MPEG4 ) + { // MPEG4 max num/den is 65535 each + ost->st->time_base.num = 1000; + ost->st->time_base.den = fps1000; + } + else + { + ost->st->time_base.num = usec; + ost->st->time_base.den = 1000000u; + } c->time_base = ost->st->time_base; c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = AV_PIX_FMT_YUV420P; From dffab9e57f813a5ac565b317d72d2ee22ce54ea3 Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Fri, 17 Sep 2021 20:43:11 -0400 Subject: [PATCH 17/19] Sync libav video/audio encoder selections to config. --- src/drivers/Qt/AviRecord.cpp | 190 +++++++++++++++++++++++++++----- src/drivers/Qt/AviRecord.h | 4 + src/drivers/Qt/MovieOptions.cpp | 8 +- src/drivers/Qt/config.cpp | 10 +- 4 files changed, 182 insertions(+), 30 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index cbb9c961..be40a847 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -48,6 +48,7 @@ extern "C" { #include "libavutil/opt.h" +#include "libavutil/pixdesc.h" #include "libavformat/avformat.h" #include "libavcodec/avcodec.h" //#include "libavresample/avresample.h" @@ -882,7 +883,7 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) c->time_base = ost->st->time_base; c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = AV_PIX_FMT_YUV420P; - //c->pix_fmt = AV_PIX_FMT_BGR24; + c->pix_fmt = AV_PIX_FMT_BGR24; //c->sample_aspect_ratio = (AVRational){ 4, 3 }; //printf("compression_level:%i\n", c->compression_level); @@ -1139,8 +1140,59 @@ static void print_Codecs(void) } } +static int setCodecFromConfig(void) +{ + std::string s; + const AVCodec *c; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT( 59, 0, 0 ) + const AVOutputFormat *fmt; +#else + AVOutputFormat *fmt; +#endif + + fmt = av_guess_format("avi", NULL, NULL); + + g_config->getOption("SDL.AviFFmpegVideoCodec", &s); + + if ( s.size() > 0 ) + { + c = avcodec_find_encoder_by_name(s.c_str()); + + if ( c ) + { + video_st.selEnc = c->id; + } + } + + g_config->getOption("SDL.AviFFmpegAudioCodec", &s); + + if ( s.size() > 0 ) + { + c = avcodec_find_encoder_by_name(s.c_str()); + + if ( c ) + { + audio_st.selEnc = c->id; + } + } + + if ( fmt ) + { + if ( video_st.selEnc == AV_CODEC_ID_NONE ) + { + video_st.selEnc = fmt->video_codec; + } + if ( audio_st.selEnc == AV_CODEC_ID_NONE ) + { + audio_st.selEnc = fmt->audio_codec; + } + } + return 0; +} + static int initMedia( const char *filename ) { + #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT( 59, 0, 0 ) const AVOutputFormat *fmt; #else @@ -1156,8 +1208,8 @@ static int initMedia( const char *filename ) if (fmt == NULL) { - printf("Could not deduce output format from file extension: using MPEG.\n"); - fmt = av_guess_format("mpeg", NULL, NULL); + printf("Could not deduce output format from file extension: using AVI.\n"); + fmt = av_guess_format("avi", NULL, NULL); } if (fmt == NULL) { @@ -1174,22 +1226,9 @@ static int initMedia( const char *filename ) } oc->oformat = fmt; - //strncpy(oc->filename, filename, sizeof(oc->filename)); - - if ( video_st.selEnc == AV_CODEC_ID_NONE ) - { - video_st.selEnc = fmt->video_codec; - } - if ( audio_st.selEnc == AV_CODEC_ID_NONE ) - { - audio_st.selEnc = fmt->audio_codec; - } + setCodecFromConfig(); if ( initVideoStream( video_st.selEnc, &video_st ) ) - //if ( initVideoStream( AV_CODEC_ID_H264, &video_st ) ) - //if ( initVideoStream( AV_CODEC_ID_FFV1, &video_st ) ) - //if ( initVideoStream( AV_CODEC_ID_RV20, &video_st ) ) - //if ( initVideoStream( AV_CODEC_ID_RAWVIDEO, &video_st ) ) { printf("Video Stream Init Failed\n"); return -1; @@ -2074,25 +2113,41 @@ LibavOptionsPage::LibavOptionsPage(QWidget *parent) : QWidget(parent) { QLabel *lbl; - QVBoxLayout *vbox1; + QVBoxLayout *vbox, *vbox1; QHBoxLayout *hbox; + LIBAV::setCodecFromConfig(); + vbox1 = new QVBoxLayout(); + videoGbox = new QGroupBox( tr("Video:") ); + audioGbox = new QGroupBox( tr("Audio:") ); + + audioGbox->setCheckable(true); + videoEncSel = new QComboBox(); audioEncSel = new QComboBox(); - hbox = new QHBoxLayout(); - vbox1->addLayout(hbox); - lbl = new QLabel( tr("Video Encoder:") ); - hbox->addWidget( lbl ); - hbox->addWidget( videoEncSel ); + vbox1->addWidget( videoGbox ); + vbox1->addWidget( audioGbox ); + + vbox = new QVBoxLayout(); + videoGbox->setLayout(vbox); + + hbox = new QHBoxLayout(); + vbox->addLayout(hbox); + lbl = new QLabel( tr("Encoder:") ); + hbox->addWidget( lbl, 1 ); + hbox->addWidget( videoEncSel, 5 ); + + vbox = new QVBoxLayout(); + audioGbox->setLayout(vbox); hbox = new QHBoxLayout(); - vbox1->addLayout(hbox); - lbl = new QLabel( tr("Audio Encoder:") ); - hbox->addWidget( lbl ); - hbox->addWidget( audioEncSel ); + vbox->addLayout(hbox); + lbl = new QLabel( tr("Encoder:") ); + hbox->addWidget( lbl, 1 ); + hbox->addWidget( audioEncSel, 5 ); initCodecLists(); @@ -2105,6 +2160,56 @@ LibavOptionsPage::LibavOptionsPage(QWidget *parent) LibavOptionsPage::~LibavOptionsPage(void) { +} +//----------------------------------------------------- +void LibavOptionsPage::initPixelFormatSelect(int codec_id) +{ + const AVCodec *c; + const AVPixFmtDescriptor *desc; + + c = avcodec_find_encoder( (AVCodecID)codec_id); + + if ( c == NULL ) + { + return; + } + if ( c->pix_fmts ) + { + int i=0, formatOk=0; + while (c->pix_fmts[i] != -1) + { + desc = av_pix_fmt_desc_get( c->pix_fmts[i] ); + + if ( desc ) + { + printf("Codec PIX_FMT: %i: %s 0x%04X\t- %s\n", c->pix_fmts[i], + desc->name, av_get_pix_fmt_loss(c->pix_fmts[i], AV_PIX_FMT_BGRA, 0), desc->alias); + } + i++; + } + //if ( !formatOk ) + //{ + // printf("CODEC Does Not Support PIX_FMT:%i Changing to:%i\n", c->pix_fmt, codec->pix_fmts[0] ); + // c->pix_fmt = codec->pix_fmts[0]; + //} + } + else + { + desc = av_pix_fmt_desc_next( NULL ); + + while ( desc != NULL ) + { + AVPixelFormat pf; + + pf = av_pix_fmt_desc_get_id(desc); + + printf("Codec PIX_FMT: %i: %s 0x%04X\t- %s\n", + pf, desc->name, av_get_pix_fmt_loss(pf, AV_PIX_FMT_BGRA, 0), desc->alias); + + desc = av_pix_fmt_desc_next( desc ); + } + } + } //----------------------------------------------------- void LibavOptionsPage::initCodecLists(void) @@ -2129,6 +2234,11 @@ void LibavOptionsPage::initCodecLists(void) if ( compatible ) { videoEncSel->addItem( tr(c->name), c->id ); + + if ( LIBAV::video_st.selEnc == c->id ) + { + videoEncSel->setCurrentIndex( videoEncSel->count() - 1 ); + } } } else if ( c->type == AVMEDIA_TYPE_AUDIO ) @@ -2138,22 +2248,48 @@ void LibavOptionsPage::initCodecLists(void) if ( compatible ) { audioEncSel->addItem( tr(c->name), c->id ); + + if ( LIBAV::audio_st.selEnc == c->id ) + { + audioEncSel->setCurrentIndex( audioEncSel->count() - 1 ); + } } } } c = av_codec_iterate( &it ); } + + initPixelFormatSelect( LIBAV::video_st.selEnc ); } //----------------------------------------------------- void LibavOptionsPage::videoCodecChanged(int idx) { + const AVCodec *c; + LIBAV::video_st.selEnc = (AVCodecID)videoEncSel->itemData(idx).toInt(); + + c = avcodec_find_encoder( LIBAV::video_st.selEnc ); + + if ( c ) + { + g_config->setOption("SDL.AviFFmpegVideoCodec", c->name); + } + initPixelFormatSelect( LIBAV::video_st.selEnc ); } //----------------------------------------------------- void LibavOptionsPage::audioCodecChanged(int idx) { + const AVCodec *c; + LIBAV::audio_st.selEnc = (AVCodecID)audioEncSel->itemData(idx).toInt(); + + c = avcodec_find_encoder( LIBAV::audio_st.selEnc ); + + if ( c ) + { + g_config->setOption("SDL.AviFFmpegAudioCodec", c->name); + } } //----------------------------------------------------- #endif diff --git a/src/drivers/Qt/AviRecord.h b/src/drivers/Qt/AviRecord.h index 79504a2f..c336fa67 100644 --- a/src/drivers/Qt/AviRecord.h +++ b/src/drivers/Qt/AviRecord.h @@ -12,6 +12,7 @@ #include #include #include +#include enum aviEncoderList { @@ -83,8 +84,11 @@ class LibavOptionsPage : public QWidget protected: QComboBox *videoEncSel; QComboBox *audioEncSel; + QGroupBox *videoGbox; + QGroupBox *audioGbox; void initCodecLists(void); + void initPixelFormatSelect(int codec_id); private slots: void videoCodecChanged(int idx); diff --git a/src/drivers/Qt/MovieOptions.cpp b/src/drivers/Qt/MovieOptions.cpp index 36a2e3f2..e1707b2f 100644 --- a/src/drivers/Qt/MovieOptions.cpp +++ b/src/drivers/Qt/MovieOptions.cpp @@ -41,7 +41,7 @@ //---------------------------------------------------------------------------- MovieOptionsDialog_t::MovieOptionsDialog_t(QWidget *parent) - : QDialog(parent) + : QDialog(parent, Qt::Window) { QLabel *lbl; QGroupBox *gbox; @@ -130,6 +130,12 @@ MovieOptionsDialog_t::MovieOptionsDialog_t(QWidget *parent) aviPageStack->addWidget( new QWidget() ); break; } + + if ( i == aviGetSelVideoFormat() ) + { + aviBackend->setCurrentIndex(i); + aviPageStack->setCurrentIndex(i); + } } setLayout(mainLayout); diff --git a/src/drivers/Qt/config.cpp b/src/drivers/Qt/config.cpp index b1a75bce..15c9211b 100644 --- a/src/drivers/Qt/config.cpp +++ b/src/drivers/Qt/config.cpp @@ -595,14 +595,20 @@ InitConfig() config->addOption("recordhud", "SDL.RecordHUD", 0); config->addOption("moviemsg", "SDL.MovieMsg", 0); -#ifdef _USE_X264 - config->addOption("SDL.AviVideoFormat", AVI_X264); +#ifdef _USE_LIBAV + config->addOption("SDL.AviVideoFormat", AVI_LIBAV); #elif WIN32 config->addOption("SDL.AviVideoFormat", AVI_VFW); +#elif _USE_X264 + config->addOption("SDL.AviVideoFormat", AVI_X264); #else config->addOption("SDL.AviVideoFormat", AVI_RGB24); #endif +#ifdef _USE_LIBAV + config->addOption("SDL.AviFFmpegVideoCodec", ""); + config->addOption("SDL.AviFFmpegAudioCodec", ""); +#endif #ifdef WIN32 config->addOption("SDL.AviVfwFccHandler", ""); #endif From 4501292f7b1d97ba946446edd8252f5a2d40782b Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Fri, 17 Sep 2021 21:43:45 -0400 Subject: [PATCH 18/19] Bug fix for finding encoder. Search by string name instead of integer ID since the name is the only unique parameter. --- src/drivers/Qt/AviRecord.cpp | 55 ++++++++++++++++++------------------ src/drivers/Qt/AviRecord.h | 2 +- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index be40a847..68050591 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -749,7 +749,7 @@ struct OutputStream int64_t next_pts; int bytesPerSample; int frameSize; - AVCodecID selEnc; + std::string selEnc; OutputStream(void) { @@ -761,7 +761,6 @@ struct OutputStream bytesPerSample = 0; frameSize = 0; next_pts = 0; - selEnc = AV_CODEC_ID_NONE; } void close(void) @@ -815,7 +814,7 @@ static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height) return picture; } -static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) +static int initVideoStream( const char *codec_name, OutputStream *ost ) { int ret; const AVCodec *codec; @@ -831,7 +830,7 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) fps1000 = (int)(fps * 1000.0); /* find the video encoder */ - codec = avcodec_find_encoder(codec_id); + codec = avcodec_find_encoder_by_name(codec_name); if (codec == NULL) { @@ -870,7 +869,7 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) * timebase should be 1/framerate and timestamp increments should be * identical to 1. */ //ost->st->time_base = (AVRational){ 1000, fps1000 }; - if ( codec_id == AV_CODEC_ID_MPEG4 ) + if ( codec->id == AV_CODEC_ID_MPEG4 ) { // MPEG4 max num/den is 65535 each ost->st->time_base.num = 1000; ost->st->time_base.den = fps1000; @@ -882,8 +881,7 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) } c->time_base = ost->st->time_base; c->gop_size = 12; /* emit one intra frame every twelve frames at most */ - c->pix_fmt = AV_PIX_FMT_YUV420P; - c->pix_fmt = AV_PIX_FMT_BGR24; + c->pix_fmt = AV_PIX_FMT_YUV420P; // Every video encoder seems to accept this //c->sample_aspect_ratio = (AVRational){ 4, 3 }; //printf("compression_level:%i\n", c->compression_level); @@ -891,6 +889,9 @@ static int initVideoStream( enum AVCodecID codec_id, OutputStream *ost ) if ( codec->pix_fmts ) { + // Auto select least lossy format to comvert to. + c->pix_fmt = avcodec_find_best_pix_fmt_of_list( codec->pix_fmts, AV_PIX_FMT_BGRA, 0, NULL); + int i=0, formatOk=0; while (codec->pix_fmts[i] != -1) { @@ -1006,14 +1007,14 @@ static AVFrame *alloc_audio_frame(enum AVSampleFormat sample_fmt, return frame; } -static int initAudioStream( enum AVCodecID codec_id, OutputStream *ost ) +static int initAudioStream( const char *codec_name, OutputStream *ost ) { int ret, nb_samples; const AVCodec *codec; AVCodecContext *c; /* find the audio encoder */ - codec = avcodec_find_encoder(codec_id); + codec = avcodec_find_encoder_by_name(codec_name); if (codec == NULL) { @@ -1160,7 +1161,7 @@ static int setCodecFromConfig(void) if ( c ) { - video_st.selEnc = c->id; + video_st.selEnc = c->name; } } @@ -1172,19 +1173,19 @@ static int setCodecFromConfig(void) if ( c ) { - audio_st.selEnc = c->id; + audio_st.selEnc = c->name; } } if ( fmt ) { - if ( video_st.selEnc == AV_CODEC_ID_NONE ) + if ( video_st.selEnc.size() == 0 ) { - video_st.selEnc = fmt->video_codec; + video_st.selEnc = avcodec_get_name(fmt->video_codec); } - if ( audio_st.selEnc == AV_CODEC_ID_NONE ) + if ( audio_st.selEnc.size() == 0 ) { - audio_st.selEnc = fmt->audio_codec; + audio_st.selEnc = avcodec_get_name(fmt->audio_codec); } } return 0; @@ -1228,12 +1229,12 @@ static int initMedia( const char *filename ) setCodecFromConfig(); - if ( initVideoStream( video_st.selEnc, &video_st ) ) + if ( initVideoStream( video_st.selEnc.c_str(), &video_st ) ) { printf("Video Stream Init Failed\n"); return -1; } - if ( initAudioStream( audio_st.selEnc, &audio_st ) ) + if ( initAudioStream( audio_st.selEnc.c_str(), &audio_st ) ) { printf("Audio Stream Init Failed\n"); return -1; @@ -2162,12 +2163,12 @@ LibavOptionsPage::~LibavOptionsPage(void) } //----------------------------------------------------- -void LibavOptionsPage::initPixelFormatSelect(int codec_id) +void LibavOptionsPage::initPixelFormatSelect(const char *codec_name) { const AVCodec *c; const AVPixFmtDescriptor *desc; - c = avcodec_find_encoder( (AVCodecID)codec_id); + c = avcodec_find_encoder_by_name( codec_name ); if ( c == NULL ) { @@ -2235,7 +2236,7 @@ void LibavOptionsPage::initCodecLists(void) { videoEncSel->addItem( tr(c->name), c->id ); - if ( LIBAV::video_st.selEnc == c->id ) + if ( strcmp( LIBAV::video_st.selEnc.c_str(), c->name ) == 0 ) { videoEncSel->setCurrentIndex( videoEncSel->count() - 1 ); } @@ -2249,7 +2250,7 @@ void LibavOptionsPage::initCodecLists(void) { audioEncSel->addItem( tr(c->name), c->id ); - if ( LIBAV::audio_st.selEnc == c->id ) + if ( strcmp( LIBAV::audio_st.selEnc.c_str(), c->name ) == 0 ) { audioEncSel->setCurrentIndex( audioEncSel->count() - 1 ); } @@ -2260,31 +2261,31 @@ void LibavOptionsPage::initCodecLists(void) c = av_codec_iterate( &it ); } - initPixelFormatSelect( LIBAV::video_st.selEnc ); + initPixelFormatSelect( videoEncSel->currentText().toStdString().c_str() ); } //----------------------------------------------------- void LibavOptionsPage::videoCodecChanged(int idx) { const AVCodec *c; - LIBAV::video_st.selEnc = (AVCodecID)videoEncSel->itemData(idx).toInt(); + LIBAV::video_st.selEnc = videoEncSel->currentText().toStdString().c_str(); - c = avcodec_find_encoder( LIBAV::video_st.selEnc ); + c = avcodec_find_encoder_by_name( LIBAV::video_st.selEnc.c_str() ); if ( c ) { g_config->setOption("SDL.AviFFmpegVideoCodec", c->name); + initPixelFormatSelect( c->name ); } - initPixelFormatSelect( LIBAV::video_st.selEnc ); } //----------------------------------------------------- void LibavOptionsPage::audioCodecChanged(int idx) { const AVCodec *c; - LIBAV::audio_st.selEnc = (AVCodecID)audioEncSel->itemData(idx).toInt(); + LIBAV::audio_st.selEnc = audioEncSel->currentText().toStdString().c_str(); - c = avcodec_find_encoder( LIBAV::audio_st.selEnc ); + c = avcodec_find_encoder_by_name( LIBAV::audio_st.selEnc.c_str() ); if ( c ) { diff --git a/src/drivers/Qt/AviRecord.h b/src/drivers/Qt/AviRecord.h index c336fa67..f2138bd6 100644 --- a/src/drivers/Qt/AviRecord.h +++ b/src/drivers/Qt/AviRecord.h @@ -88,7 +88,7 @@ class LibavOptionsPage : public QWidget QGroupBox *audioGbox; void initCodecLists(void); - void initPixelFormatSelect(int codec_id); + void initPixelFormatSelect(const char *codec_name); private slots: void videoCodecChanged(int idx); From 435c332902d2bab8c2e6fd71797ddddce43ad98f Mon Sep 17 00:00:00 2001 From: mjbudd77 Date: Fri, 17 Sep 2021 22:26:22 -0400 Subject: [PATCH 19/19] Bug fix for auto-detection of default encoders for libav. --- src/drivers/Qt/AviRecord.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/drivers/Qt/AviRecord.cpp b/src/drivers/Qt/AviRecord.cpp index 68050591..6f94cfbb 100644 --- a/src/drivers/Qt/AviRecord.cpp +++ b/src/drivers/Qt/AviRecord.cpp @@ -1018,7 +1018,7 @@ static int initAudioStream( const char *codec_name, OutputStream *ost ) if (codec == NULL) { - fprintf(stderr, "codec not found\n"); + fprintf(stderr, "codec not found: '%s'\n", codec_name); return -1; } @@ -1181,11 +1181,21 @@ static int setCodecFromConfig(void) { if ( video_st.selEnc.size() == 0 ) { - video_st.selEnc = avcodec_get_name(fmt->video_codec); + c = avcodec_find_encoder( fmt->video_codec ); + + if ( c ) + { + video_st.selEnc = c->name; + } } if ( audio_st.selEnc.size() == 0 ) { - audio_st.selEnc = avcodec_get_name(fmt->audio_codec); + c = avcodec_find_encoder( fmt->audio_codec ); + + if ( c ) + { + audio_st.selEnc = c->name; + } } } return 0;