all repos — mgba @ d7968ccc0ddc0beb93d8b4044d2324a5e390f391

mGBA Game Boy Advance Emulator

Merge branch 'ffmpeg'
Jeffrey Pfau jeffrey@endrift.com
Tue, 12 Aug 2014 22:19:54 -0700
commit

d7968ccc0ddc0beb93d8b4044d2324a5e390f391

parent

9ed4fad33cf1a9cef4f03ee5682bb50df6d9b428

M CMakeLists.txtCMakeLists.txt

@@ -5,6 +5,7 @@ set(CMAKE_C_FLAGS_DEBUG "-g -Wall -Wextra -std=gnu99")

set(CMAKE_C_FLAGS_RELEASE "-O3 -Wall -Wextra -std=gnu99") set(USE_CLI_DEBUGGER ON CACHE BOOL "Whether or not to enable the CLI-mode ARM debugger") set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugger") +set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support") set(BUILD_SDL ON CACHE BOOL "Build SDL frontend") set(BUILD_PERF ON CACHE BOOL "Build performance profiling tool") file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c)

@@ -96,6 +97,15 @@ add_definitions(-DUSE_GDB_STUB)

list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c) endif() source_group("ARM debugger" FILES ${DEBUGGER_SRC}) + +if(USE_FFMPEG) + pkg_search_module(LIBAVCODEC libavcodec REQUIRED) + pkg_search_module(LIBAVFORMAT libavformat REQUIRED) + pkg_search_module(LIBAVUTIL libavutil REQUIRED) + add_definitions(-DUSE_FFMPEG) + list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c") + list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVUTIL_LIBRARIES}) +endif() find_package(PNG REQUIRED) find_package(ZLIB REQUIRED)
M src/gba/gba-audio.csrc/gba/gba-audio.c

@@ -704,6 +704,10 @@ GBASyncLockAudio(audio->p->sync);

CircleBufferWrite32(&audio->left, sampleLeft); CircleBufferWrite32(&audio->right, sampleRight); unsigned produced = CircleBufferSize(&audio->left); + struct GBAThread* thread = GBAThreadGetContext(); + if (thread && thread->stream) { + thread->stream->postAudioFrame(thread->stream, sampleLeft, sampleRight); + } GBASyncProduceAudio(audio->p->sync, produced >= CircleBufferCapacity(&audio->left) / sizeof(int32_t) * 3); }
M src/gba/gba-thread.csrc/gba/gba-thread.c

@@ -559,6 +559,9 @@ thread->rewindBufferNext = thread->rewindBufferInterval;

GBARecordFrame(thread); } } + if (thread->stream) { + thread->stream->postVideoFrame(thread->stream, thread->renderer); + } if (thread->frameCallback) { thread->frameCallback(thread); }
M src/gba/gba-thread.hsrc/gba/gba-thread.h

@@ -39,6 +39,11 @@ Condition audioRequiredCond;

Mutex audioBufferMutex; }; +struct GBAAVStream { + void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer); + void (*postAudioFrame)(struct GBAAVStream*, int32_t left, int32_t right); +}; + struct GBAThread { // Output enum ThreadState state;

@@ -58,6 +63,7 @@ struct VFile* patch;

const char* fname; int activeKeys; struct GBAInputMap inputMap; + struct GBAAVStream* stream; // Run-time options int frameskip;
A src/platform/ffmpeg/ffmpeg-encoder.c

@@ -0,0 +1,197 @@

+#include "ffmpeg-encoder.h" + +#include "gba-video.h" + +#include <libavutil/imgutils.h> + +static void _ffmpegPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); +static void _ffmpegPostAudioFrame(struct GBAAVStream*, int32_t left, int32_t right); + +void FFmpegEncoderInit(struct FFmpegEncoder* encoder) { + av_register_all(); + + encoder->d.postVideoFrame = _ffmpegPostVideoFrame; + encoder->d.postAudioFrame = _ffmpegPostAudioFrame; + + FFmpegEncoderSetAudio(encoder, "flac", 0); + FFmpegEncoderSetVideo(encoder, "png", 0); + encoder->currentAudioSample = 0; + encoder->currentAudioFrame = 0; + encoder->currentVideoFrame = 0; +} + +bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, unsigned abr) { + if (!avcodec_find_encoder_by_name(acodec)) { + return false; + } + encoder->audioCodec = acodec; + encoder->audioBitrate = abr; + return true; +} + +bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, unsigned vbr) { + AVCodec* codec = avcodec_find_encoder_by_name(vcodec); + if (!codec) { + return false; + } + + size_t i; + encoder->pixFormat = AV_PIX_FMT_NONE; + for (i = 0; codec->pix_fmts[i] != AV_PIX_FMT_NONE; ++i) { + if (codec->pix_fmts[i] == AV_PIX_FMT_RGB24) { + encoder->pixFormat = AV_PIX_FMT_RGB24; + break; + } + if (codec->pix_fmts[i] == AV_PIX_FMT_BGR0) { + encoder->pixFormat = AV_PIX_FMT_BGR0; + } + } + if (encoder->pixFormat == AV_PIX_FMT_NONE) { + return false; + } + encoder->videoCodec = vcodec; + encoder->videoBitrate = vbr; + return true; +} + +bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { + AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec); + AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec); + if (!acodec || !vcodec) { + return false; + } + + avformat_alloc_output_context2(&encoder->context, 0, 0, outfile); + + encoder->audioStream = avformat_new_stream(encoder->context, acodec); + encoder->audio = encoder->audioStream->codec; + encoder->audio->bit_rate = encoder->audioBitrate; + encoder->audio->sample_rate = 0x8000; + encoder->audio->channels = 2; + encoder->audio->channel_layout = AV_CH_LAYOUT_STEREO; + encoder->audio->sample_fmt = AV_SAMPLE_FMT_S16; + avcodec_open2(encoder->audio, acodec, 0); + encoder->audioFrame = av_frame_alloc(); + encoder->audioFrame->nb_samples = encoder->audio->frame_size; + encoder->audioFrame->format = encoder->audio->sample_fmt; + encoder->audioFrame->pts = 0; + encoder->audioBufferSize = av_samples_get_buffer_size(0, encoder->audio->channels, encoder->audio->frame_size, encoder->audio->sample_fmt, 0); + encoder->audioBuffer = av_malloc(encoder->audioBufferSize); + avcodec_fill_audio_frame(encoder->audioFrame, encoder->audio->channels, encoder->audio->sample_fmt, (const uint8_t*) encoder->audioBuffer, encoder->audioBufferSize, 0); + + encoder->videoStream = avformat_new_stream(encoder->context, vcodec); + encoder->video = encoder->videoStream->codec; + encoder->video->bit_rate = encoder->videoBitrate; + encoder->video->width = VIDEO_HORIZONTAL_PIXELS; + encoder->video->height = VIDEO_VERTICAL_PIXELS; + encoder->video->time_base = (AVRational) { VIDEO_TOTAL_LENGTH, GBA_ARM7TDMI_FREQUENCY }; + encoder->video->pix_fmt = encoder->pixFormat; + encoder->video->gop_size = 15; + encoder->video->max_b_frames = 0; + avcodec_open2(encoder->video, vcodec, 0); + encoder->videoFrame = av_frame_alloc(); + encoder->videoFrame->format = encoder->video->pix_fmt; + encoder->videoFrame->width = encoder->video->width; + encoder->videoFrame->height = encoder->video->height; + encoder->videoFrame->pts = 0; + av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->video->width, encoder->video->height, encoder->video->pix_fmt, 32); + + if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) { + encoder->audio->flags |= CODEC_FLAG_GLOBAL_HEADER; + encoder->video->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + + avio_open(&encoder->context->pb, outfile, AVIO_FLAG_WRITE); + avformat_write_header(encoder->context, 0); + + return true; +} + +void FFmpegEncoderClose(struct FFmpegEncoder* encoder) { + av_write_trailer(encoder->context); + avio_close(encoder->context->pb); + + av_free(encoder->audioBuffer); + av_frame_free(&encoder->audioFrame); + avcodec_close(encoder->audio); + + av_frame_free(&encoder->videoFrame); + avcodec_close(encoder->video); + avformat_free_context(encoder->context); +} + +void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) { + struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream; + + av_frame_make_writable(encoder->audioFrame); + encoder->audioBuffer[encoder->currentAudioSample * 2] = left; + encoder->audioBuffer[encoder->currentAudioSample * 2 + 1] = right; + encoder->audioFrame->pts = av_rescale_q(encoder->currentAudioFrame, encoder->audio->time_base, encoder->audioStream->time_base); + ++encoder->currentAudioFrame; + ++encoder->currentAudioSample; + + if ((encoder->currentAudioSample * 4) < encoder->audioBufferSize) { + return; + } + encoder->currentAudioSample = 0; + + AVPacket packet; + av_init_packet(&packet); + packet.data = 0; + packet.size = 0; + int gotData; + avcodec_encode_audio2(encoder->audio, &packet, encoder->audioFrame, &gotData); + if (gotData) { + packet.stream_index = encoder->audioStream->index; + av_interleaved_write_frame(encoder->context, &packet); + } + av_free_packet(&packet); +} + +void _ffmpegPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) { + struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream; + uint32_t* pixels; + unsigned stride; + renderer->getPixels(renderer, &stride, (void**) &pixels); + + AVPacket packet; + + av_init_packet(&packet); + packet.data = 0; + packet.size = 0; + av_frame_make_writable(encoder->videoFrame); + encoder->videoFrame->pts = av_rescale_q(encoder->currentVideoFrame, encoder->video->time_base, encoder->videoStream->time_base); + ++encoder->currentVideoFrame; + + unsigned x, y; + if (encoder->videoFrame->format == AV_PIX_FMT_BGR0) { + for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { + uint32_t pixel = pixels[stride * y + x]; + encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 4] = pixel >> 16; + encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 4 + 1] = pixel >> 8; + encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 4 + 2] = pixel; + } + } + } else if (encoder->videoFrame->format == AV_PIX_FMT_RGB24) { + for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { + uint32_t pixel = pixels[stride * y + x]; + encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 3] = pixel; + encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 3 + 1] = pixel >> 8; + encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 3 + 2] = pixel >> 16; + } + } + } + + int gotData; + avcodec_encode_video2(encoder->video, &packet, encoder->videoFrame, &gotData); + if (gotData) { + if (encoder->videoStream->codec->coded_frame->key_frame) { + packet.flags |= AV_PKT_FLAG_KEY; + } + packet.stream_index = encoder->videoStream->index; + av_interleaved_write_frame(encoder->context, &packet); + } + av_free_packet(&packet); +}
A src/platform/ffmpeg/ffmpeg-encoder.h

@@ -0,0 +1,40 @@

+#ifndef FFMPEG_ENCODER +#define FFMPEG_ENCODER + +#include "gba-thread.h" + +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> + +struct FFmpegEncoder { + struct GBAAVStream d; + AVFormatContext* context; + + unsigned audioBitrate; + const char* audioCodec; + + unsigned videoBitrate; + const char* videoCodec; + + AVCodecContext* audio; + uint16_t* audioBuffer; + size_t audioBufferSize; + AVFrame* audioFrame; + size_t currentAudioSample; + int64_t currentAudioFrame; + AVStream* audioStream; + + AVCodecContext* video; + enum AVPixelFormat pixFormat; + AVFrame* videoFrame; + int64_t currentVideoFrame; + AVStream* videoStream; +}; + +void FFmpegEncoderInit(struct FFmpegEncoder*); +bool FFmpegEncoderSetAudio(struct FFmpegEncoder*, const char* acodec, unsigned abr); +bool FFmpegEncoderSetVideo(struct FFmpegEncoder*, const char* vcodec, unsigned vbr); +bool FFmpegEncoderOpen(struct FFmpegEncoder*, const char* outfile); +void FFmpegEncoderClose(struct FFmpegEncoder*); + +#endif