diff --git a/CMakeLists.txt b/CMakeLists.txt index adb0d59c..433d7b16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -280,7 +280,7 @@ set( if(ENABLE_FFMPEG) find_package(PkgConfig REQUIRED) - pkg_check_modules(FFMPEG REQUIRED libavcodec libavformat libswscale libavutil) + pkg_check_modules(FFMPEG REQUIRED libavcodec libavformat libswscale libavutil libswresample) if(FFMPEG_STATIC) set(FFMPEG_LIBRARIES ${FFMPEG_STATIC_LIBRARIES}) @@ -625,7 +625,7 @@ set( ) if(MSVC) - set(SRC_MAIN ${SRC_MAIN} "dependencies/msvc/getopt.c") + set(SRC_MAIN ${SRC_MAIN} "dependencies/msvc/getopt.c") endif() set( @@ -643,7 +643,7 @@ set( ) if(MSVC) - set(HDR_MAIN ${HDR_MAIN} "dependencies/msvc/getopt.h") + set(HDR_MAIN ${HDR_MAIN} "dependencies/msvc/getopt.h") endif() if(ENABLE_FFMPEG) diff --git a/src/common/ffmpeg.cpp b/src/common/ffmpeg.cpp index 3b33f1cf..b941ba3e 100644 --- a/src/common/ffmpeg.cpp +++ b/src/common/ffmpeg.cpp @@ -1,613 +1,617 @@ -// this code has been partially lifted from the output-example.c program in -// libavformat. Not much of that original code remains. - -// unlike the rest of the wx code, this has no wx dependency at all, and -// could be used by other front ends as well. - -#define __STDC_LIMIT_MACROS // required for ffmpeg -#define __STDC_CONSTANT_MACROS // required for ffmpeg - -#include "../gba/Sound.h" -extern "C" { -#include -#include -#include -#include -#include -#ifndef AV_PKT_FLAG_KEY -#define AV_PKT_FLAG_KEY PKT_FLAG_KEY -#endif -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,96,0) -// note that there is no sane way to easily free a context w/o free_context() -// so this will probably leak -static void avformat_free_context(AVFormatContext *ctx) -{ - if(ctx->pb) - url_fclose(ctx->pb); - for(int i = 0; i < ctx->nb_streams; i++) { - if(ctx->streams[i]->codec) - avcodec_close(ctx->streams[i]->codec); - av_freep(&ctx->streams[i]->codec); - av_freep(&ctx->streams[i]); - } - av_free(ctx); -} -#endif -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,45,0) -#define av_guess_format guess_format -#endif -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,105,0) -#define avio_open url_fopen -#define avio_close url_fclose -#endif -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52,64,0) -#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO -#define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO -#endif -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(50,1,0) -// this will almost definitely fail on big-endian systems -#define PIX_FMT_RGB565LE PIX_FMT_RGB565 -#endif -#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(50,38,0) -#define AV_SAMPLE_FMT_S16 SAMPLE_FMT_S16 -#endif -} - -// For compatibility with 3.0+ ffmpeg -#include -#ifndef PixelFormat -#define PixelFormat AVPixelFormat -#endif -#if LIBAVCODEC_VERSION_MAJOR >= 56 -#define CODEC_ID_NONE AV_CODEC_ID_NONE -#define CODEC_ID_PCM_S16LE AV_CODEC_ID_PCM_S16LE -#define CODEC_ID_PCM_S16BE AV_CODEC_ID_PCM_S16BE -#define CODEC_ID_PCM_U16LE AV_CODEC_ID_PCM_U16LE -#define CODEC_ID_PCM_U16BE AV_CODEC_ID_PCM_U16BE -#endif -#if LIBAVCODEC_VERSION_MAJOR > 56 -#define CODEC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER -#endif -#if LIBAVUTIL_VERSION_MAJOR > 54 -#define avcodec_alloc_frame av_frame_alloc -#define PIX_FMT_RGB565LE AV_PIX_FMT_RGB565LE -#define PIX_FMT_RGB24 AV_PIX_FMT_RGB24 -#define PIX_FMT_RGBA AV_PIX_FMT_RGBA -#endif - -#define priv_AVFormatContext AVFormatContext -#define priv_AVStream AVStream -#define priv_AVOutputFormat AVOutputFormat -#define priv_AVFrame AVFrame -#define priv_SwsContext SwsContext -#define priv_PixelFormat PixelFormat #include "ffmpeg.h" -// I have no idea what size to make these buffers -// I don't see any ffmpeg functions to guess the size, either +#define STREAM_FRAME_RATE 60 +#define STREAM_PIXEL_FORMAT AV_PIX_FMT_YUV420P +#define IN_SOUND_FORMAT AV_SAMPLE_FMT_S16 -#ifdef AV_INPUT_BUFFER_MIN_SIZE +struct supportedCodecs { + AVCodecID codecId; + char const *longName; + char const *exts; +}; - // use frame size, or AV_INPUT_BUFFER_MIN_SIZE (that seems to be what it wants) -#define AUDIO_BUF_LEN (frame_len > AV_INPUT_BUFFER_MIN_SIZE ? frame_len : AV_INPUT_BUFFER_MIN_SIZE) - // use maximum frame size * 32 bpp * 2 for good measure -#define VIDEO_BUF_LEN (AV_INPUT_BUFFER_MIN_SIZE + 256 * 244 * 4 * 2) +const supportedCodecs audioSupported[] = { + { AV_CODEC_ID_MP3, "MP3 (MPEG audio layer 3)", "mp3" } +}; -#else +const supportedCodecs videoSupported[] = { + { AV_CODEC_ID_MPEG4, "AVI (Audio Video Interleaved)", "avi" }, + { AV_CODEC_ID_MPEG4, "raw MPEG-4 video", "m4v" } +}; - // use frame size, or FF_MIN_BUFFER_SIZE (that seems to be what it wants) -#define AUDIO_BUF_LEN (frame_len > FF_MIN_BUFFER_SIZE ? frame_len : FF_MIN_BUFFER_SIZE) - // use maximum frame size * 32 bpp * 2 for good measure -#define VIDEO_BUF_LEN (FF_MIN_BUFFER_SIZE + 256 * 244 * 4 * 2) - -#endif - -bool MediaRecorder::did_init = false; - -MediaRecorder::MediaRecorder() : oc(0), vid_st(0), aud_st(0), video_buf(0), - audio_buf(0), audio_buf2(0), converter(0), convpic(0) +std::vector recording::getSupVidNames() { - if(!did_init) { - did_init = true; - av_register_all(); - } - pic = avcodec_alloc_frame(); + std::vector result; + size_t size = sizeof(videoSupported) / sizeof(videoSupported[0]); + for (size_t i = 0; i < size; ++i) + result.push_back((char *)videoSupported[i].longName); + return result; } -MediaRet MediaRecorder::setup_sound_stream(const char *fname, AVOutputFormat *fmt) +std::vector recording::getSupVidExts() { - oc = avformat_alloc_context(); - if(!oc) - return MRET_ERR_NOMEM; - oc->oformat = fmt; - strncpy(oc->filename, fname, sizeof(oc->filename) - 1); - oc->filename[sizeof(oc->filename) - 1] = 0; - if(fmt->audio_codec == CODEC_ID_NONE) - return MRET_OK; - - AVCodecContext *ctx; -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,10,0) - aud_st = av_new_stream(oc, 1); -#else - aud_st = avformat_new_stream(oc, NULL); -#endif - if(!aud_st) { - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_NOMEM; - } - - AVCodec *codec = avcodec_find_encoder(fmt->audio_codec); - - if (!codec) { - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_NOCODEC; - } - - ctx = aud_st->codec; - ctx->codec_id = fmt->audio_codec; - ctx->codec_type = AVMEDIA_TYPE_AUDIO; - // Some encoders don't like int16_t (SAMPLE_FMT_S16) - ctx->sample_fmt = codec->sample_fmts[0]; - // This was changed in the initial ffmpeg 3.0 update, - // but shouldn't (as far as I'm aware) cause problems with older versions - ctx->bit_rate = 128000; // arbitrary; in case we're generating mp3 - ctx->sample_rate = soundGetSampleRate(); - ctx->channels = 2; - ctx->time_base.den = 60; - ctx->time_base.num = 1; - if(fmt->flags & AVFMT_GLOBALHEADER) - ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; - -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53,6,0) - if(avcodec_open(ctx, codec)) { -#else - if(avcodec_open2(ctx, codec, NULL)) { -#endif - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_NOCODEC; - } - - return MRET_OK; + std::vector result; + size_t size = sizeof(videoSupported) / sizeof(videoSupported[0]); + for (size_t i = 0; i < size; ++i) + result.push_back((char *)videoSupported[i].exts); + return result; } -MediaRet MediaRecorder::setup_video_stream(const char *fname, int w, int h, int d) +std::vector recording::getSupAudNames() { - AVCodecContext *ctx; -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,10,0) - vid_st = av_new_stream(oc, 0); -#else - vid_st = avformat_new_stream(oc, NULL); -#endif - if(!vid_st) { - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_NOMEM; - } - ctx = vid_st->codec; - ctx->codec_id = oc->oformat->video_codec; - ctx->codec_type = AVMEDIA_TYPE_VIDEO; - ctx->width = w; - ctx->height = h; - ctx->time_base.den = 60; - ctx->time_base.num = 1; - // dunno if any of these help; some output just looks plain crappy - // will have to investigate further - ctx->bit_rate = 400000; - ctx->gop_size = 12; - ctx->max_b_frames = 2; - switch(d) { - case 16: - // FIXME: test & make endian-neutral - pixfmt = PIX_FMT_RGB565LE; - break; - case 24: - pixfmt = PIX_FMT_RGB24; - break; - case 32: - default: // should never be anything else - pixfmt = PIX_FMT_RGBA; - break; - } - ctx->pix_fmt = pixfmt; - pixsize = d >> 3; - linesize = pixsize * w; - ctx->max_b_frames = 2; - if(oc->oformat->flags & AVFMT_GLOBALHEADER) - ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; - - AVCodec *codec = avcodec_find_encoder(oc->oformat->video_codec); - // make sure RGB is supported (mostly not) - if(codec->pix_fmts) { - const enum PixelFormat *p; -#if LIBAVCODEC_VERSION_MAJOR < 55 - int64_t mask = 0; -#endif - for(p = codec->pix_fmts; *p != -1; p++) { - // may get complaints about 1LL; thus the cast -#if LIBAVCODEC_VERSION_MAJOR < 55 - mask |= ((int64_t)1) << *p; -#endif - if(*p == pixfmt) - break; - } - if(*p == -1) { - // if not supported, use a converter to the next best format - // this is swscale, the converter used by the output demo -#if LIBAVCODEC_VERSION_MAJOR < 55 - enum PixelFormat dp = (PixelFormat)avcodec_find_best_pix_fmt(mask, pixfmt, 0, NULL); -#else -#if LIBAVCODEC_VERSION_MICRO >= 100 -// FFmpeg - enum AVPixelFormat dp = avcodec_find_best_pix_fmt_of_list(codec->pix_fmts, pixfmt, 0, NULL); -#else -// Libav - enum AVPixelFormat dp = avcodec_find_best_pix_fmt2(codec->pix_fmts, pixfmt, 0, NULL); -#endif -#endif - if(dp == -1) - dp = codec->pix_fmts[0]; - if(!(convpic = avcodec_alloc_frame()) || - avpicture_alloc((AVPicture *)convpic, dp, w, h) < 0) { - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_NOMEM; - } -#if LIBSWSCALE_VERSION_INT < AV_VERSION_INT(0, 12, 0) - converter = sws_getContext(w, h, pixfmt, w, h, dp, SWS_BICUBIC, - NULL, NULL, NULL); -#else - converter = sws_alloc_context(); - // what a convoluted, inefficient way to set options - av_opt_set_int(converter, "sws_flags", SWS_BICUBIC, 0); - av_opt_set_int(converter, "srcw", w, 0); - av_opt_set_int(converter, "srch", h, 0); - av_opt_set_int(converter, "dstw", w, 0); - av_opt_set_int(converter, "dsth", h, 0); - av_opt_set_int(converter, "src_format", pixfmt, 0); - av_opt_set_int(converter, "dst_format", dp, 0); - sws_init_context(converter, NULL, NULL); -#endif - ctx->pix_fmt = dp; - } - } -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53,6,0) - if(!codec || avcodec_open(ctx, codec)) { -#else - if(!codec || avcodec_open2(ctx, codec, NULL)) { -#endif - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_NOCODEC; - } - - return MRET_OK; + std::vector result; + size_t size = sizeof(audioSupported) / sizeof(audioSupported[0]); + for (size_t i = 0; i < size; ++i) + result.push_back((char *)audioSupported[i].longName); + return result; } -MediaRet MediaRecorder::finish_setup(const char *fname) +std::vector recording::getSupAudExts() { - if(audio_buf) - free(audio_buf); - if(audio_buf2) - free(audio_buf2); - audio_buf2 = NULL; - in_audio_buf2 = 0; - if(aud_st) { - frame_len = aud_st->codec->frame_size * 4; - sample_len = soundGetSampleRate() * 4 / 60; - switch(aud_st->codec->codec_id) { - case CODEC_ID_PCM_S16LE: - case CODEC_ID_PCM_S16BE: - case CODEC_ID_PCM_U16LE: - case CODEC_ID_PCM_U16BE: - frame_len = sample_len; - } - audio_buf = (uint8_t *)malloc(AUDIO_BUF_LEN); - if(!audio_buf) { - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_NOMEM; - } - if(frame_len != sample_len && (frame_len > sample_len || sample_len % frame_len)) { - audio_buf2 = (uint16_t *)malloc(frame_len); - if(!audio_buf2) { - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_NOMEM; - } - } - } else - audio_buf = NULL; - if(video_buf) - free(video_buf); - if(vid_st) { - video_buf = (uint8_t *)malloc(VIDEO_BUF_LEN); - if(!video_buf) { - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_NOMEM; - } - } else { - video_buf = NULL; - } - if(!(oc->oformat->flags & AVFMT_NOFILE)) { - if(avio_open(&oc->pb, fname, AVIO_FLAG_WRITE) < 0) { - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_FERR; - } - } - avformat_write_header(oc, NULL); - return MRET_OK; + std::vector result; + size_t size = sizeof(audioSupported) / sizeof(audioSupported[0]); + for (size_t i = 0; i < size; ++i) + result.push_back((char *)audioSupported[i].exts); + return result; } -MediaRet MediaRecorder::Record(const char *fname, int width, int height, int depth) -{ - if(oc) - return MRET_ERR_RECORDING; - aud_st = vid_st = NULL; - AVOutputFormat *fmt = av_guess_format(NULL, fname, NULL); - if(!fmt) - fmt = av_guess_format("avi", NULL, NULL); - if(!fmt || fmt->video_codec == CODEC_ID_NONE) - return MRET_ERR_FMTGUESS; - MediaRet ret; - if((ret = setup_sound_stream(fname, fmt)) == MRET_OK && - (ret = setup_video_stream(fname, width, height, depth)) == MRET_OK) - ret = finish_setup(fname); - return ret; -} -MediaRet MediaRecorder::Record(const char *fname) -{ - if(oc) - return MRET_ERR_RECORDING; - aud_st = vid_st = NULL; - AVOutputFormat *fmt = av_guess_format(NULL, fname, NULL); - if(!fmt) - fmt = av_guess_format("wav", NULL, NULL); - if(!fmt || fmt->audio_codec == CODEC_ID_NONE) - return MRET_ERR_FMTGUESS; - MediaRet ret; - if((ret = setup_sound_stream(fname, fmt)) == MRET_OK) - ret = finish_setup(fname); - return ret; -} +// avoid 'error: taking address of temporary array' +// for debug function when compiling +#ifdef av_err2str +#undef av_err2str +#define av_err2str(errnum) av_make_error_string((char*)__builtin_alloca(AV_ERROR_MAX_STRING_SIZE), AV_ERROR_MAX_STRING_SIZE, errnum) +#endif -void MediaRecorder::Stop() -{ - if(oc) { - if(in_audio_buf2) - AddFrame((uint16_t *)0); - av_write_trailer(oc); - avformat_free_context(oc); - oc = NULL; - } - if(audio_buf) { - free(audio_buf); - audio_buf = NULL; - } - if(video_buf) { - free(video_buf); - video_buf = NULL; - } - if(audio_buf2) { - free(audio_buf2); - audio_buf2 = NULL; - } - if(convpic) { - avpicture_free((AVPicture *)convpic); - av_free(convpic); - convpic = NULL; - } - if(converter) { - sws_freeContext(converter); - converter = NULL; - } -} +#ifdef av_ts2str +#undef av_ts2str +#define av_ts2str(ts) av_ts_make_string((char*)__builtin_alloca(AV_TS_MAX_STRING_SIZE), ts) +#endif -MediaRecorder::~MediaRecorder() +#ifdef av_ts2timestr +#undef av_ts2timestr +#define av_ts2timestr(ts,tb) av_ts_make_time_string((char*)__builtin_alloca(AV_TS_MAX_STRING_SIZE), ts, tb) +#endif + +// debug function +//static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt) +//{ +// AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base; +// fprintf(stderr, "pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n", +// av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base), +// av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base), +// av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base), +// pkt->stream_index); +//} + +recording::MediaRecorder::~MediaRecorder() { Stop(); } -// Still needs updating for avcodec_encode_video2 -MediaRet MediaRecorder::AddFrame(const uint8_t *vid) +recording::MediaRet recording::MediaRecorder::setup_audio_stream() { - if(!oc || !vid_st) - return MRET_OK; - - AVCodecContext *ctx = vid_st->codec; - AVPacket pkt; -#if LIBAVCODEC_VERSION_MAJOR >= 56 - int ret, got_packet = 0; -#endif - - // strip borders. inconsistent between depths for some reason - // but fortunately consistent between gb/gba. - int tbord, rbord; - switch(pixsize) { - case 2: - // 16-bit: 2 @ right, 1 @ top - tbord = 1; rbord = 2; break; - case 3: - // 24-bit: no border - tbord = rbord = 0; break; - case 4: - // 32-bit: 1 @ right, 1 @ top - tbord = 1; rbord = 1; break; - } - avpicture_fill((AVPicture *)pic, (uint8_t *)vid + tbord * (linesize + pixsize * rbord), - (PixelFormat)pixfmt, ctx->width + rbord, ctx->height); - // satisfy stupid sws_scale()'s integrity check - pic->data[1] = pic->data[2] = pic->data[3] = pic->data[0]; - pic->linesize[1] = pic->linesize[2] = pic->linesize[3] = pic->linesize[0]; - - AVFrame *f = pic; - - if(converter) { - sws_scale(converter, pic->data, pic->linesize, 0, ctx->height, - convpic->data, convpic->linesize); - f = convpic; - } - av_init_packet(&pkt); - pkt.stream_index = vid_st->index; -#ifdef AVFMT_RAWPICTURE - if(oc->oformat->flags & AVFMT_RAWPICTURE) { - // this won't work due to border - // not sure what formats set this, anyway - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.data = f->data[0]; - pkt.size = linesize * ctx->height; - } else { -#endif -#if LIBAVCODEC_VERSION_MAJOR >= 56 - pkt.data = video_buf; - pkt.size = VIDEO_BUF_LEN; - f->format = ctx->pix_fmt; - f->width = ctx->width; - f->height = ctx->height; - ret = avcodec_encode_video2(ctx, &pkt, f, &got_packet); - if(!ret && got_packet && ctx->coded_frame) { - ctx->coded_frame->pts = pkt.pts; - ctx->coded_frame->key_frame = !!(pkt.flags & AV_PKT_FLAG_KEY); + // audio stream + ast = avformat_new_stream(oc, NULL); + if (!ast) return MRET_ERR_BUFSIZE; + ast->id = oc->nb_streams - 1; + // audio codec + acodec = avcodec_find_encoder(fmt->audio_codec); + if (!acodec) return MRET_ERR_NOCODEC; + // audio codec context + aenc = avcodec_alloc_context3(acodec); + if (!aenc) return MRET_ERR_BUFSIZE; + aenc->sample_fmt = acodec->sample_fmts ? acodec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; + aenc->bit_rate = 128000; // mp3 + aenc->sample_rate = sampleRate; + if (acodec->supported_samplerates) + { + aenc->sample_rate = acodec->supported_samplerates[0]; + for (int i = 0; acodec->supported_samplerates[i]; ++i) + { + if (acodec->supported_samplerates[i] == 44100) + aenc->sample_rate = 44100; } -#else - pkt.size = avcodec_encode_video(ctx, video_buf, VIDEO_BUF_LEN, f); -#endif - if(!pkt.size) - return MRET_OK; - if(ctx->coded_frame && ctx->coded_frame->pts != AV_NOPTS_VALUE) - pkt.pts = av_rescale_q(ctx->coded_frame->pts, ctx->time_base, vid_st->time_base); - if(pkt.size > VIDEO_BUF_LEN) { - avformat_free_context(oc); - oc = NULL; - return MRET_ERR_BUFSIZE; - } - if(ctx->coded_frame->key_frame) - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.data = video_buf; -#ifdef AVFMT_RAWPICTURE } -#endif - if(av_interleaved_write_frame(oc, &pkt) < 0) { - avformat_free_context(oc); - oc = NULL; - // yeah, err might not be a file error, but if it isn't, it's a - // coding error rather than a user-controllable error - // and better resolved using debugging - return MRET_ERR_FERR; + aenc->channels = av_get_channel_layout_nb_channels(aenc->channel_layout); + aenc->channel_layout = AV_CH_LAYOUT_STEREO; + if (acodec->channel_layouts) + { + aenc->channel_layout = acodec->channel_layouts[0]; + for (int i = 0; acodec->channel_layouts[i]; ++i) + { + if (acodec->channel_layouts[i] == AV_CH_LAYOUT_STEREO) + aenc->channel_layout = AV_CH_LAYOUT_STEREO; + } + } + aenc->channels = av_get_channel_layout_nb_channels(aenc->channel_layout); + aenc->time_base = (AVRational){ 1, aenc->sample_rate }; + ast->time_base = (AVRational){ 1, STREAM_FRAME_RATE }; + // open and use codec on stream + int nb_samples; + if (avcodec_open2(aenc, acodec, NULL) < 0) + return MRET_ERR_NOCODEC; + if (avcodec_parameters_from_context(ast->codecpar, aenc) < 0) + return MRET_ERR_BUFSIZE; + // number of samples per frame + if (aenc->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) + nb_samples = 10000; + else + nb_samples = aenc->frame_size; + // audio frame for input + audioframeTmp = av_frame_alloc(); + if (!audioframeTmp) return MRET_ERR_BUFSIZE; + audioframeTmp->format = IN_SOUND_FORMAT; + audioframeTmp->channel_layout = aenc->channel_layout; + audioframeTmp->sample_rate = aenc->sample_rate; + audioframeTmp->nb_samples = nb_samples; + if (nb_samples) + { + if (av_frame_get_buffer(audioframeTmp, 0) < 0) + return MRET_ERR_BUFSIZE; + } + // audio frame for output + audioframe = av_frame_alloc(); + if (!audioframe) return MRET_ERR_BUFSIZE; + audioframe->format = aenc->sample_fmt; + audioframe->channel_layout = aenc->channel_layout; + audioframe->sample_rate = aenc->sample_rate; + audioframe->nb_samples = nb_samples; + if (nb_samples) + { + if (av_frame_get_buffer(audioframe, 0) < 0) + return MRET_ERR_BUFSIZE; + } + // initialize the converter + swr = swr_alloc(); + if (!swr) + { + return MRET_ERR_BUFSIZE; + } + av_opt_set_int (swr, "in_channel_count", aenc->channels, 0); + av_opt_set_int (swr, "in_sample_rate", aenc->sample_rate, 0); + av_opt_set_sample_fmt(swr, "in_sample_fmt", IN_SOUND_FORMAT, 0); + av_opt_set_int (swr, "out_channel_count", aenc->channels, 0); + av_opt_set_int (swr, "out_sample_rate", aenc->sample_rate, 0); + av_opt_set_sample_fmt(swr, "out_sample_fmt", aenc->sample_fmt, 0); + if (swr_init(swr) < 0) + { + fprintf(stderr, "Failed to initialize the resampling context\n"); + return MRET_ERR_BUFSIZE; + } + // auxiliary buffer for setting up frames for encode + audioBufferSize = nb_samples * aenc->channels * sizeof(uint16_t); + audioBuffer = (uint16_t *) calloc(nb_samples * aenc->channels, sizeof(uint16_t)); + if (!audioBuffer) return MRET_ERR_BUFSIZE; + samplesInAudioBuffer = 0; + posInAudioBuffer = 0; + return MRET_OK; +} + +recording::MediaRet recording::MediaRecorder::setup_video_stream_info(int width, int height, int depth) +{ + switch (depth) + { + case 16: + // FIXME: test & make endian-neutral + pixfmt = AV_PIX_FMT_RGB565LE; + break; + case 24: + pixfmt = AV_PIX_FMT_RGB24; + break; + case 32: + pixfmt = AV_PIX_FMT_RGBA; + break; + default: // should never be anything else + pixfmt = AV_PIX_FMT_RGBA; + break; + } + // initialize the converter + sws = sws_getContext(width, height, pixfmt, // from + width, height, STREAM_PIXEL_FORMAT, // to + SWS_BICUBIC, NULL, NULL, NULL); // params + if (!sws) return MRET_ERR_BUFSIZE; + // getting info about frame + pixsize = depth >> 3; + linesize = pixsize * width; + switch (pixsize) + { + case 2: + // 16-bit: 2 @ right, 1 @ top + tbord = 1; rbord = 2; + break; + case 3: + // 24-bit: no border + tbord = rbord = 0; + break; + case 4: + // 32-bit: 1 @ right, 1 @ top + tbord = rbord = 1; + break; + default: + break; } return MRET_OK; } -#if LIBAVCODEC_VERSION_MAJOR >= 56 -/* FFmpeg depricated avcodec_encode_audio. - * It was removed completely in 3.0. - * This will at least get audio recording *working* - */ -static inline int MediaRecorderEncodeAudio(AVCodecContext *ctx, - AVPacket *pkt, - uint8_t *buf, int buf_size, - const short *samples) +recording::MediaRet recording::MediaRecorder::setup_video_stream(int width, int height) { - AVFrame *frame; - av_init_packet(pkt); - int ret, samples_size, got_packet = 0; + // video stream + st = avformat_new_stream(oc, NULL); + if (!st) return MRET_ERR_NOMEM; + st->id = oc->nb_streams - 1; + st->time_base = (AVRational){ 1, STREAM_FRAME_RATE }; + // video codec + vcodec = avcodec_find_encoder(fmt->video_codec); + if (!vcodec) return MRET_ERR_FMTGUESS; + // codec context + enc = avcodec_alloc_context3(vcodec); + enc->codec_id = fmt->video_codec; + enc->bit_rate = 400000; // arbitrary + enc->width = width; + enc->height = height; + enc->time_base = st->time_base; + enc->gop_size = 12; + enc->pix_fmt = STREAM_PIXEL_FORMAT; + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + enc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + if (enc->codec_id == AV_CODEC_ID_MPEG2VIDEO) + enc->max_b_frames = 2; + if (enc->codec_id == AV_CODEC_ID_MPEG1VIDEO) + enc->mb_decision = 2; + // open and use codec on stream + if (avcodec_open2(enc, vcodec, NULL) < 0) return MRET_ERR_NOCODEC; + if (avcodec_parameters_from_context(st->codecpar, enc) < 0) return MRET_ERR_BUFSIZE; + // frame for input + frameIn = av_frame_alloc(); + if (!frameIn) return MRET_ERR_NOMEM; + frameIn->format = pixfmt; + frameIn->width = width; + frameIn->height = height; + if (av_frame_get_buffer(frameIn, 32) < 0) return MRET_ERR_NOMEM; + // frame for output + frameOut = av_frame_alloc(); + if (!frameOut) return MRET_ERR_NOMEM; + frameOut->format = STREAM_PIXEL_FORMAT; + frameOut->width = width; + frameOut->height = height; + if (av_frame_get_buffer(frameOut, 32) < 0) return MRET_ERR_NOMEM; + return MRET_OK; +} - pkt->data = buf; - pkt->size = buf_size; - if (samples) { - frame = frame = av_frame_alloc(); - if (ctx->frame_size) { - frame->nb_samples = ctx->frame_size; - } else { - frame->nb_samples = (int64_t)buf_size * 8 / - (av_get_bits_per_sample(ctx->codec_id) * - ctx->channels); - } - frame->format = ctx->sample_fmt; - frame->channel_layout = ctx->channel_layout; - samples_size = av_samples_get_buffer_size(NULL, ctx->channels, - frame->nb_samples, ctx->sample_fmt, 1); - avcodec_fill_audio_frame(frame, ctx->channels, ctx->sample_fmt, - (const uint8_t *)samples, samples_size, 1); - //frame->pts = AV_NOPTS_VALUE; - } else { - frame = NULL; +recording::MediaRet recording::MediaRecorder::finish_setup(const char *fname) +{ + av_dump_format(oc, 0, fname, 1); + // open the output file + if (!(fmt->flags & AVFMT_NOFILE)) + { + if (avio_open(&oc->pb, fname, AVIO_FLAG_WRITE) < 0) + return MRET_ERR_FERR; } - ret = avcodec_encode_audio2(ctx, pkt, frame, &got_packet); - if (!ret && got_packet && ctx->coded_frame) { - ctx->coded_frame->pts = pkt->pts; - ctx->coded_frame->key_frame = !!(pkt->flags & AV_PKT_FLAG_KEY); - } - if (frame && frame->extended_data != frame->data) - av_freep(&frame->extended_data); + // write the stream header + if (avformat_write_header(oc, NULL) < 0) return MRET_ERR_FERR; + return MRET_OK; +} + +recording::MediaRecorder::MediaRecorder() : isRecording(false), + sampleRate(44100), oc(NULL), fmt(NULL), audioOnlyRecording(false) +{ + // pic info + pixfmt = AV_PIX_FMT_NONE; + pixsize = linesize = -1; + tbord = rbord = 0; + sws = NULL; + // stream info + st = NULL; + vcodec = NULL; + enc = NULL; + npts = 0; + frameIn = frameOut = NULL; + // audio setup + swr = NULL; + acodec = NULL; + ast = NULL; + aenc = NULL; + samplesCount = 0; + audioframe = NULL; + audioframeTmp = NULL; + audioBuffer = NULL; + posInAudioBuffer = 0; + samplesInAudioBuffer = 0; + audioBufferSize = 0; +} + +// video : return error code to user +recording::MediaRet recording::MediaRecorder::Record(const char *fname, int width, int height, int depth) +{ + MediaRet ret; + if (isRecording) return MRET_ERR_RECORDING; + isRecording = true; + // initial setup + ret = setup_common(fname); + if (ret != MRET_OK) + { + Stop(); return ret; - -} -#endif - -MediaRet MediaRecorder::AddFrame(const uint16_t *aud) -{ - if(!oc || !aud_st) - return MRET_OK; - // aud == NULL means just flush out last frame - if(!aud && !in_audio_buf2) - return MRET_OK; - AVCodecContext *ctx = aud_st->codec; - AVPacket pkt; - - int len = sample_len; - if(in_audio_buf2) { - int ncpy = frame_len - in_audio_buf2; - if(ncpy > len) - ncpy = len; - if(aud) { - memcpy(audio_buf2 + in_audio_buf2/2, aud, ncpy); - len -= ncpy; - aud += ncpy / 2; - } else { - memset(audio_buf2 + in_audio_buf2/2, 0, ncpy); - len = 0; - } - in_audio_buf2 += ncpy; } - while(len + in_audio_buf2 >= frame_len) { - av_init_packet(&pkt); - #if LIBAVCODEC_VERSION_MAJOR >= 56 - MediaRecorderEncodeAudio(ctx, &pkt, audio_buf, frame_len, - #else - pkt.size = avcodec_encode_audio(ctx, audio_buf, frame_len, - #endif - (const short *)(in_audio_buf2 ? audio_buf2 : aud)); - if(ctx->coded_frame && ctx->coded_frame->pts != AV_NOPTS_VALUE) - pkt.pts = av_rescale_q(ctx->coded_frame->pts, ctx->time_base, aud_st->time_base); - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.stream_index = aud_st->index; - #if LIBAVCODEC_VERSION_MAJOR < 57 - pkt.data = audio_buf; - #endif - if(av_interleaved_write_frame(oc, &pkt) < 0) { - avformat_free_context(oc); - oc = NULL; - // yeah, err might not be a file error, but if it isn't, it's a - // coding error rather than a user-controllable error - // and better resolved using debugging - return MRET_ERR_FERR; - } - if(in_audio_buf2) - in_audio_buf2 = 0; - else { - aud += frame_len / 2; - len -= frame_len; - } + // video stream + ret = setup_video_stream_info(width, height, depth); + if (ret != MRET_OK) + { + Stop(); + return ret; } - if(len > 0) { - memcpy(audio_buf2, aud, len); - in_audio_buf2 = len; + ret = setup_video_stream(width, height); + if (ret != MRET_OK) + { + Stop(); + return ret; + } + // audio stream + ret = setup_audio_stream(); + if (ret != MRET_OK) + { + Stop(); + return ret; + } + // last details + ret = finish_setup(fname); + if (ret != MRET_OK) + { + Stop(); + return ret; + } + return MRET_OK; +} + +recording::MediaRet recording::MediaRecorder::AddFrame(const uint8_t *vid) +{ + if (!isRecording) return MRET_OK; + // fill and encode frame variables + int got_packet = 0, ret = 0; + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + // fill frame with current pic + ret = av_image_fill_arrays(frameIn->data, frameIn->linesize, + (uint8_t *)vid + tbord * (linesize + pixsize * rbord), + pixfmt, enc->width + rbord, enc->height, 1); + if (ret < 0) return MRET_ERR_RECORDING; + // convert from input format to output + sws_scale(sws, (const uint8_t * const *) frameIn->data, + frameIn->linesize, 0, enc->height, frameOut->data, + frameOut->linesize); + // set valid pts for frame + frameOut->pts = npts++; + // finally, encode frame + got_packet = avcodec_receive_packet(enc, &pkt); + ret = avcodec_send_frame(enc, frameOut); + if (ret < 0) return MRET_ERR_RECORDING; + if (!got_packet) + { + // rescale output packet timestamp values from codec + // to stream timebase + av_packet_rescale_ts(&pkt, enc->time_base, st->time_base); + pkt.stream_index = st->index; + //log_packet(oc, &pkt); + ret = av_interleaved_write_frame(oc, &pkt); + if (ret < 0) return MRET_ERR_RECORDING; + } + return MRET_OK; +} + +void recording::MediaRecorder::Stop() +{ + if (oc) + { + // write the trailer; must be called before av_codec_close() + if (!audioOnlyRecording) + av_write_trailer(oc); + } + isRecording = false; + if (sws) + { + sws_freeContext(sws); + sws = NULL; + } + if (st) + { + st = NULL; + } + if (enc) + { + avcodec_free_context(&enc); + avcodec_close(enc); + enc = NULL; + } + if (vcodec) + { + vcodec = NULL; + } + if (frameIn) + { + av_frame_free(&frameIn); + frameIn = NULL; + } + if (frameOut) + { + av_frame_free(&frameOut); + frameOut = NULL; + } + npts = 0; + if (oc) + { + // close the output file + if (!(fmt->flags & AVFMT_NOFILE)) + { + avio_closep(&oc->pb); + } + fmt = NULL; + avformat_free_context(oc); + oc = NULL; + } + + // audio + audioOnlyRecording = false; + if (swr) + { + swr_free(&swr); + swr = NULL; + } + if (acodec) + { + acodec = NULL; + } + if (ast) + { + ast = NULL; + } + if (aenc) + { + avcodec_free_context(&aenc); + avcodec_close(aenc); + aenc = NULL; + } + samplesCount = 0; + if (audioframe) + { + av_frame_free(&audioframe); + audioframe = NULL; + } + if (audioframeTmp) + { + av_frame_free(&audioframeTmp); + audioframeTmp = NULL; + } + if (audioBuffer) + { + free(audioBuffer); + audioBuffer = NULL; + } + samplesInAudioBuffer = 0; + posInAudioBuffer = 0; +} + +recording::MediaRet recording::MediaRecorder::setup_common(const char *fname) +{ + avformat_alloc_output_context2(&oc, NULL, NULL, fname); + if (!oc) return MRET_ERR_BUFSIZE; + fmt = oc->oformat; + return MRET_OK; +} + +// audio : return error code to user +recording::MediaRet recording::MediaRecorder::Record(const char *fname) +{ + MediaRet ret; + if (isRecording) return MRET_ERR_RECORDING; + isRecording = true; + audioOnlyRecording = true; + // initial setup + ret = setup_common(fname); + if (ret != MRET_OK) + { + Stop(); + return ret; + } + // audio stream + ret = setup_audio_stream(); + if (ret != MRET_OK) + { + Stop(); + return ret; + } + // last details + ret = finish_setup(fname); + if (ret != MRET_OK) + { + Stop(); + return ret; + } + return MRET_OK; +} + +#define MIN(a,b) (((a)<(b))?(a):(b)) + +recording::MediaRet recording::MediaRecorder::AddFrame(const uint16_t *aud, int length) +{ + if (!isRecording) return MRET_OK; + AVCodecContext *c = aenc; + int samples_size = av_samples_get_buffer_size(NULL, c->channels, audioframeTmp->nb_samples, IN_SOUND_FORMAT, 1); + + int realLength = length / sizeof *aud; + bool isMissing = false; + int cp = -1; + if (samplesInAudioBuffer < c->frame_size) + { + int missingSamples = (c->frame_size - samplesInAudioBuffer); + // 2 * missingSamples =: for 2 channels + // realLength =: entire samples (1470 ~ 735 samples per channel) + int maxCopy = MIN(2 * missingSamples, realLength); + memcpy(audioBuffer + posInAudioBuffer, aud, maxCopy * 2); + posInAudioBuffer += maxCopy; + samplesInAudioBuffer += (maxCopy / 2); + aud += maxCopy; + if (maxCopy < realLength) + { + isMissing = true; + cp = realLength - maxCopy; + } + } + if (samplesInAudioBuffer != c->frame_size) // not enough samples + { + return MRET_OK; + } + + int got_packet; + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + + if (avcodec_fill_audio_frame(audioframeTmp, c->channels, IN_SOUND_FORMAT, (const uint8_t *)audioBuffer, samples_size, 1) < 0) + { + return MRET_ERR_RECORDING; + } + + int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr, c->sample_rate) + audioframeTmp->nb_samples, c->sample_rate, c->sample_rate, AV_ROUND_UP); + av_assert0(dst_nb_samples == audioframeTmp->nb_samples); + + audioframeTmp->data[3] = audioframeTmp->data[2] = audioframeTmp->data[1] = audioframeTmp->data[0]; + if (swr_convert(swr, audioframe->data, audioframe->nb_samples, (const uint8_t **)audioframeTmp->data, audioframeTmp->nb_samples) < 0) + { + return MRET_ERR_RECORDING; + } + audioframe->data[3] = audioframe->data[2] = audioframe->data[1] = audioframe->data[0]; + audioframe->pts = av_rescale_q(samplesCount, (AVRational){1, c->sample_rate}, c->time_base); + samplesCount += dst_nb_samples; + + got_packet = avcodec_receive_packet(c, &pkt); + if (avcodec_send_frame(c, audioframe) < 0) + { + return MRET_ERR_RECORDING; + } + if (!got_packet) + { + av_packet_rescale_ts(&pkt, (AVRational){ 1, c->sample_rate }, ast->time_base); + pkt.stream_index = ast->index; + //log_packet(oc, &pkt); + if (av_interleaved_write_frame(oc, &pkt) < 0) + { + return MRET_ERR_RECORDING; + } + } + // if we are missing part of the sample, adjust here + // for next iteration + posInAudioBuffer = 0; + samplesInAudioBuffer = 0; + memset(audioBuffer, 0, audioBufferSize); + if (isMissing) + { + memcpy(audioBuffer, aud, cp * 2); + posInAudioBuffer = cp; + samplesInAudioBuffer = (cp / 2); } return MRET_OK; } diff --git a/src/common/ffmpeg.h b/src/common/ffmpeg.h index a8c9ac64..cc6094b4 100644 --- a/src/common/ffmpeg.h +++ b/src/common/ffmpeg.h @@ -3,19 +3,36 @@ // simplified interface for recording audio and/or video from emulator -// unlike the rest of the wx code, this has no wx dependency at all, and -// could be used by other front ends as well. +// required for ffmpeg +#define __STDC_LIMIT_MACROS +#define __STDC_CONSTANT_MACROS -// this only supports selecting output format via file name extensions; -// maybe some future version will support specifying a format. wx-2.9 -// has an extra widget for the file selector, but 2.8 doesn't. +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + +#include +#include + +namespace recording { + + +// get supported audio/video codecs +std::vector getSupVidNames(); +std::vector getSupVidExts(); +std::vector getSupAudNames(); +std::vector getSupAudExts(); -// the only missing piece that I couldn't figure out how to do generically -// is the code to find the available formats & associated extensions for -// the file dialog. // return codes -// probably ought to put in own namespace, but this is good enough enum MediaRet { MRET_OK, // no errors MRET_ERR_NOMEM, // error allocating buffers or structures @@ -40,41 +57,62 @@ class MediaRecorder void Stop(); bool IsRecording() { - return oc != NULL; + return isRecording; } // add a frame of video; width+height+depth already given // assumes a 1-pixel border on top & right // always assumes being passed 1/60th of a second of video MediaRet AddFrame(const uint8_t *vid); // add a frame of audio; uses current sample rate to know length - // always assumes being passed 1/60th of a second of audio. - MediaRet AddFrame(const uint16_t *aud); + // always assumes being passed 1/60th of a second of audio; + // single sample, though (we need one for each channel). + MediaRet AddFrame(const uint16_t *aud, int length); + // set sampleRate; we need this to remove the GBA file header + // include. + void SetSampleRate(int newSampleRate) + { + sampleRate = newSampleRate; + } private: - static bool did_init; + bool isRecording; + int sampleRate; + AVFormatContext *oc; + AVOutputFormat *fmt; + // pic info + AVPixelFormat pixfmt; + int pixsize, linesize; + int tbord, rbord; + struct SwsContext *sws; + // stream info + AVStream *st; + AVCodec *vcodec; + AVCodecContext *enc; + int64_t npts; // for video frame pts + AVFrame *frameIn; + AVFrame *frameOut; + // audio + bool audioOnlyRecording; + struct SwrContext *swr; + AVCodec *acodec; + AVStream *ast; + AVCodecContext *aenc; + int samplesCount; // for audio frame pts generation + AVFrame *audioframe; + AVFrame *audioframeTmp; + // audio buffer + uint16_t *audioBuffer; + int posInAudioBuffer; + int samplesInAudioBuffer; + int audioBufferSize; -// these are to avoid polluting things with avcodec includes -#ifndef priv_AVFormatContext -#define priv_AVFormatContext void -#define priv_AVStream void -#define priv_AVOutputFormat void -#define priv_AVFrame void -#define priv_SwsContext void -#define priv_PixelFormat int -#endif - priv_AVFormatContext *oc; - priv_AVStream *vid_st, *aud_st; - uint8_t *audio_buf, *video_buf; - uint16_t *audio_buf2; - int frame_len, sample_len, in_audio_buf2; - int linesize, pixsize; - priv_PixelFormat pixfmt; - priv_AVFrame *pic, *convpic; - priv_SwsContext *converter; - - MediaRet setup_sound_stream(const char *fname, priv_AVOutputFormat *fmt); - MediaRet setup_video_stream(const char *fname, int w, int h, int d); + MediaRet setup_common(const char *fname); + MediaRet setup_video_stream_info(int width, int height, int depth); + MediaRet setup_video_stream(int width, int height); + MediaRet setup_audio_stream(); MediaRet finish_setup(const char *fname); }; +} + #endif /* WX_FFMPEG_H */ diff --git a/src/wx/cmdevents.cpp b/src/wx/cmdevents.cpp index 4ea98779..191710f1 100644 --- a/src/wx/cmdevents.cpp +++ b/src/wx/cmdevents.cpp @@ -1,8 +1,3 @@ -#ifndef NO_FFMPEG -#define __STDC_LIMIT_MACROS // required for ffmpeg -#define __STDC_CONSTANT_MACROS // required for ffmpeg -#endif - #include "wxvbam.h" #include #include @@ -15,16 +10,6 @@ #include #include -#ifndef NO_FFMPEG -extern "C" { -#include -} -// For compatibility with 3.0+ ffmpeg -#include -#if LIBAVCODEC_VERSION_MAJOR >= 56 -#define CODEC_ID_NONE AV_CODEC_ID_NONE -#endif -#endif #include "version.h" #include "../common/ConfigManager.h" #include "../gb/gbPrinter.h" @@ -828,7 +813,7 @@ EVT_HANDLER_MASK(RomInformation, "ROM information...", CMDEN_GB | CMDEN_GBA) } break; default: - break; + break; } } @@ -1179,23 +1164,20 @@ EVT_HANDLER_MASK(RecordSoundStartRecording, "Start sound recording...", CMDEN_NS if (!sound_exts.size()) { sound_extno = -1; - int extno; - AVOutputFormat* fmt; + int extno = 0; - for (fmt = NULL, extno = 0; (fmt = av_oformat_next(fmt));) { - if (!fmt->extensions) - continue; + std::vector fmts = recording::getSupAudNames(); + std::vector exts = recording::getSupAudExts(); - if (fmt->audio_codec == CODEC_ID_NONE) - continue; - - sound_exts.append(wxString(fmt->long_name ? fmt->long_name : fmt->name, wxConvLibc)); + for (size_t i = 0; i < fmts.size(); ++i) + { + sound_exts.append(wxString(fmts[i], wxConvLibc)); sound_exts.append(_(" files (")); - wxString ext(fmt->extensions, wxConvLibc); + wxString ext(exts[i], wxConvLibc); ext.Replace(wxT(","), wxT(";*.")); ext.insert(0, wxT("*.")); - if (sound_extno < 0 && ext.find(wxT("*.wav")) != wxString::npos) + if (sound_extno < 0 && ext.find(wxT("*.mp3")) != wxString::npos) sound_extno = extno; sound_exts.append(ext); @@ -1252,19 +1234,16 @@ EVT_HANDLER_MASK(RecordAVIStartRecording, "Start video recording...", CMDEN_NVRE if (!vid_exts.size()) { vid_extno = -1; - int extno; - AVOutputFormat* fmt; + int extno = 0; - for (fmt = NULL, extno = 0; (fmt = av_oformat_next(fmt));) { - if (!fmt->extensions) - continue; + std::vector fmts = recording::getSupVidNames(); + std::vector exts = recording::getSupVidExts(); - if (fmt->video_codec == CODEC_ID_NONE) - continue; - - vid_exts.append(wxString(fmt->long_name ? fmt->long_name : fmt->name, wxConvLibc)); + for (size_t i = 0; i < fmts.size(); ++i) + { + vid_exts.append(wxString(fmts[i], wxConvLibc)); vid_exts.append(_(" files (")); - wxString ext(fmt->extensions, wxConvLibc); + wxString ext(exts[i], wxConvLibc); ext.Replace(wxT(","), wxT(";*.")); ext.insert(0, wxT("*.")); diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index a522c69c..96787090 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -1016,8 +1016,8 @@ void GameArea::OnIdle(wxIdleEvent& event) // the userdata is freed on disconnect/destruction this->Connect(wxEVT_SIZE, wxSizeEventHandler(GameArea::OnSize), NULL, this); - // we need to check if the buttons stayed pressed when focus the panel - w->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(GameArea::OnKillFocus), NULL, this); + // we need to check if the buttons stayed pressed when focus the panel + w->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(GameArea::OnKillFocus), NULL, this); w->SetBackgroundStyle(wxBG_STYLE_CUSTOM); w->SetSize(wxSize(basic_width, basic_height)); @@ -1143,7 +1143,7 @@ static void clear_input_press() int i; for (i = 0; i < 4; ++i) { - joypress[i] = 0; + joypress[i] = 0; } keys_pressed.clear(); } @@ -2179,15 +2179,15 @@ void GLDrawingPanel::DrawingPanelInit() #define tex_fmt out_16 ? GL_BGRA : GL_RGBA, \ out_16 ? GL_UNSIGNED_SHORT_1_5_5_5_REV : GL_UNSIGNED_BYTE #if 0 - texsize = width > height ? width : height; - texsize = std::ceil(texsize * scale); - // texsize = 1 << ffs(texsize); - texsize = texsize | (texsize >> 1); - texsize = texsize | (texsize >> 2); - texsize = texsize | (texsize >> 4); - texsize = texsize | (texsize >> 8); - texsize = (texsize >> 1) + 1; - glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, texsize, texsize, 0, tex_fmt, NULL); + texsize = width > height ? width : height; + texsize = std::ceil(texsize * scale); + // texsize = 1 << ffs(texsize); + texsize = texsize | (texsize >> 1); + texsize = texsize | (texsize >> 2); + texsize = texsize | (texsize >> 4); + texsize = texsize | (texsize >> 8); + texsize = (texsize >> 1) + 1; + glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, texsize, texsize, 0, tex_fmt, NULL); #else // but really, most cards support non-p2 and rect // if not, use cairo or wx renderer @@ -2311,22 +2311,22 @@ void DXDrawingPanel::DrawArea(wxWindowDC& dc) #endif #ifndef NO_FFMPEG -static const wxString media_err(MediaRet ret) +static const wxString media_err(recording::MediaRet ret) { switch (ret) { - case MRET_OK: + case recording::MRET_OK: return wxT(""); - case MRET_ERR_NOMEM: + case recording::MRET_ERR_NOMEM: return _("memory allocation error"); - case MRET_ERR_NOCODEC: + case recording::MRET_ERR_NOCODEC: return _("error initializing codec"); - case MRET_ERR_FERR: + case recording::MRET_ERR_FERR: return _("error writing to output file"); - case MRET_ERR_FMTGUESS: + case recording::MRET_ERR_FMTGUESS: return _("can't guess output format from file name"); default: @@ -2338,11 +2338,11 @@ static const wxString media_err(MediaRet ret) void GameArea::StartVidRecording(const wxString& fname) { - MediaRet ret; + recording::MediaRet ret; if ((ret = vid_rec.Record(fname.mb_str(), basic_width, basic_height, systemColorDepth)) - != MRET_OK) + != recording::MRET_OK) wxLogError(_("Unable to begin recording to %s (%s)"), fname.mb_str(), media_err(ret)); else { @@ -2368,9 +2368,9 @@ void GameArea::StopVidRecording() void GameArea::StartSoundRecording(const wxString& fname) { - MediaRet ret; + recording::MediaRet ret; - if ((ret = snd_rec.Record(fname.mb_str())) != MRET_OK) + if ((ret = snd_rec.Record(fname.mb_str())) != recording::MRET_OK) wxLogError(_("Unable to begin recording to %s (%s)"), fname.mb_str(), media_err(ret)); else { @@ -2396,15 +2396,15 @@ void GameArea::StopSoundRecording() void GameArea::AddFrame(const uint16_t* data, int length) { - MediaRet ret; + recording::MediaRet ret; - if ((ret = vid_rec.AddFrame(data)) != MRET_OK) { + if ((ret = vid_rec.AddFrame(data, length)) != recording::MRET_OK) { wxLogError(_("Error in audio/video recording (%s); aborting"), media_err(ret)); vid_rec.Stop(); } - if ((ret = snd_rec.AddFrame(data)) != MRET_OK) { + if ((ret = snd_rec.AddFrame(data, length)) != recording::MRET_OK) { wxLogError(_("Error in audio recording (%s); aborting"), media_err(ret)); snd_rec.Stop(); } @@ -2412,9 +2412,9 @@ void GameArea::AddFrame(const uint16_t* data, int length) void GameArea::AddFrame(const uint8_t* data) { - MediaRet ret; + recording::MediaRet ret; - if ((ret = vid_rec.AddFrame(data)) != MRET_OK) { + if ((ret = vid_rec.AddFrame(data)) != recording::MRET_OK) { wxLogError(_("Error in video recording (%s); aborting"), media_err(ret)); vid_rec.Stop(); } diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index 186847b0..c5ec820e 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -114,7 +114,7 @@ public: wxAcceleratorEntry_v GetAccels() { - return accels; + return accels; } // the main configuration @@ -632,7 +632,7 @@ protected: void OnKillFocus(wxFocusEvent& ev); #ifndef NO_FFMPEG - MediaRecorder snd_rec, vid_rec; + recording::MediaRecorder snd_rec, vid_rec; #endif public: