diff --git a/record/ffemu.c b/record/ffemu.c index fd6926130f..13c78c50e8 100644 --- a/record/ffemu.c +++ b/record/ffemu.c @@ -11,6 +11,8 @@ #include #include #include "ffemu.h" +#include "fifo_buffer.h" +#include "SDL_thread.h" struct video_info { @@ -57,13 +59,23 @@ struct ffemu struct muxer_info muxer; struct ffemu_params params; + + SDL_cond *cond; + SDL_mutex *cond_lock; + SDL_mutex *lock; + fifo_buffer_t *audio_fifo; + fifo_buffer_t *video_fifo; + fifo_buffer_t *attr_fifo; + SDL_Thread *thread; + volatile bool alive; + volatile bool can_sleep; }; -static int init_audio(struct audio_info *audio, struct ffemu_params *param) +static bool init_audio(struct audio_info *audio, struct ffemu_params *param) { AVCodec *codec = avcodec_find_encoder(CODEC_ID_FLAC); if (!codec) - return -1; + return false; audio->codec = avcodec_alloc_context(); avcodec_get_context_defaults(audio->codec); @@ -73,25 +85,25 @@ static int init_audio(struct audio_info *audio, struct ffemu_params *param) audio->codec->channels = param->channels; audio->codec->sample_fmt = AV_SAMPLE_FMT_S16; if (avcodec_open(audio->codec, codec) != 0) - return -1; + return false; audio->buffer = av_malloc(audio->codec->frame_size * param->channels * sizeof(int16_t)); if (!audio->buffer) - return -1; + return false; audio->outbuf_size = 200000; audio->outbuf = av_malloc(audio->outbuf_size); if (!audio->outbuf) - return -1; + return false; - return 0; + return true; } -static int init_video(struct video_info *video, struct ffemu_params *param) +static bool init_video(struct video_info *video, struct ffemu_params *param) { - AVCodec *codec = avcodec_find_encoder(CODEC_ID_HUFFYUV); + AVCodec *codec = avcodec_find_encoder(CODEC_ID_FFV1); if (!codec) - return -1; + return false; video->codec = avcodec_alloc_context(); video->codec->width = param->out_width; @@ -101,7 +113,7 @@ static int init_video(struct video_info *video, struct ffemu_params *param) video->codec->sample_aspect_ratio = av_d2q(param->aspect_ratio * param->out_height / param->out_width, 255); if (avcodec_open(video->codec, codec) != 0) - return -1; + return false; // Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be. video->outbuf_size = 5000000; @@ -113,10 +125,10 @@ static int init_video(struct video_info *video, struct ffemu_params *param) video->conv_frame = avcodec_alloc_frame(); avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, PIX_FMT_RGB32, 512, 448); - return 0; + return true; } -static int init_muxer(ffemu_t *handle) +static bool init_muxer(ffemu_t *handle) { AVFormatContext *ctx = avformat_alloc_context(); av_strlcpy(ctx->filename, handle->params.filename, sizeof(ctx->filename)); @@ -124,7 +136,7 @@ static int init_muxer(ffemu_t *handle) if (avio_open(&ctx->pb, ctx->filename, URL_WRONLY) < 0) { av_free(ctx); - return -1; + return false; } int stream_cnt = 0; @@ -144,10 +156,53 @@ static int init_muxer(ffemu_t *handle) handle->muxer.astream = stream; if (av_write_header(ctx) < 0) - return -1; + return false; handle->muxer.ctx = ctx; - return 0; + return true; +} + +#define MAX_FRAMES 64 + +static int SDLCALL ffemu_thread(void *data); + +static bool init_thread(ffemu_t *handle) +{ + handle->lock = SDL_CreateMutex(); + handle->cond_lock = SDL_CreateMutex(); + handle->cond = SDL_CreateCond(); + handle->audio_fifo = fifo_new(32000 * sizeof(int16_t) * handle->params.channels * MAX_FRAMES / 60); + handle->attr_fifo = fifo_new(sizeof(struct ffemu_video_data) * MAX_FRAMES); + handle->video_fifo = fifo_new(512 * 448 * sizeof(int16_t) * MAX_FRAMES); + + handle->alive = true; + handle->can_sleep = true; + handle->thread = SDL_CreateThread(ffemu_thread, handle); + + return true; +} + +static void deinit_thread(ffemu_t *handle) +{ + if (handle->thread) + { + SDL_mutexP(handle->cond_lock); + handle->alive = false; + handle->can_sleep = false; + SDL_mutexV(handle->cond_lock); + + SDL_CondSignal(handle->cond); + SDL_WaitThread(handle->thread, NULL); + handle->thread = NULL; + + SDL_DestroyMutex(handle->lock); + SDL_DestroyMutex(handle->cond_lock); + SDL_DestroyCond(handle->cond); + + fifo_free(handle->audio_fifo); + fifo_free(handle->attr_fifo); + fifo_free(handle->video_fifo); + } } ffemu_t *ffemu_new(const struct ffemu_params *params) @@ -161,13 +216,16 @@ ffemu_t *ffemu_new(const struct ffemu_params *params) handle->params = *params; - if (init_video(&handle->video, &handle->params) < 0) + if (!init_video(&handle->video, &handle->params)) goto error; - if (init_audio(&handle->audio, &handle->params) < 0) + if (!init_audio(&handle->audio, &handle->params)) goto error; - if (init_muxer(handle) < 0) + if (!init_muxer(handle)) + goto error; + + if (!init_thread(handle)) goto error; return handle; @@ -181,6 +239,8 @@ void ffemu_free(ffemu_t *handle) { if (handle) { + deinit_thread(handle); + if (handle->audio.codec) { avcodec_close(handle->audio.codec); @@ -209,8 +269,78 @@ void ffemu_free(ffemu_t *handle) } } -// Need to make this thread based, but hey. int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data) +{ + for (;;) + { + SDL_mutexP(handle->lock); + unsigned avail = fifo_write_avail(handle->attr_fifo); + SDL_mutexV(handle->lock); + + if (!handle->alive) + return -1; + + if (avail >= sizeof(*data)) + break; + + SDL_mutexP(handle->cond_lock); + if (handle->can_sleep) + { + handle->can_sleep = false; + SDL_CondWait(handle->cond, handle->cond_lock); + handle->can_sleep = true; + } + else + SDL_CondSignal(handle->cond); + + SDL_mutexV(handle->cond_lock); + } + + SDL_mutexP(handle->lock); + fifo_write(handle->attr_fifo, data, sizeof(*data)); + fifo_write(handle->video_fifo, data->data, data->pitch * data->height); + SDL_mutexV(handle->lock); + SDL_CondSignal(handle->cond); + + return 0; +} + +int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data) +{ + for (;;) + { + SDL_mutexP(handle->lock); + unsigned avail = fifo_write_avail(handle->audio_fifo); + SDL_mutexV(handle->lock); + + if (!handle->alive) + return -1; + + if (avail >= data->frames * handle->params.channels * sizeof(int16_t)) + break; + + SDL_mutexP(handle->cond_lock); + if (handle->can_sleep) + { + handle->can_sleep = false; + SDL_CondWait(handle->cond, handle->cond_lock); + handle->can_sleep = true; + } + else + SDL_CondSignal(handle->cond); + + SDL_mutexV(handle->cond_lock); + } + + SDL_mutexP(handle->lock); + fifo_write(handle->audio_fifo, data->data, data->frames * handle->params.channels * sizeof(int16_t)); + SDL_mutexV(handle->lock); + SDL_CondSignal(handle->cond); + + return 0; +} + +static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_data *data) { handle->video.sws_ctx = sws_getCachedContext(handle->video.sws_ctx, data->width, data->height, PIX_FMT_RGB555LE, handle->params.out_width, handle->params.out_height, PIX_FMT_RGB32, SWS_POINT, @@ -250,9 +380,8 @@ int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data) return 0; } -int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data) +static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_data *data) { - size_t written_frames = 0; while (written_frames < data->frames) { @@ -279,13 +408,9 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data) pkt.size = out_size; if (handle->audio.codec->coded_frame && handle->audio.codec->coded_frame->pts != AV_NOPTS_VALUE) - { pkt.pts = av_rescale_q(handle->audio.codec->coded_frame->pts, handle->audio.codec->time_base, handle->muxer.astream->time_base); - } else - { pkt.pts = av_rescale_q(handle->audio.frame_cnt, handle->audio.codec->time_base, handle->muxer.astream->time_base); - } pkt.flags |= AV_PKT_FLAG_KEY; handle->audio.frames_in_buffer = 0; @@ -303,6 +428,8 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data) int ffemu_finalize(ffemu_t *handle) { + deinit_thread(handle); + // Push out delayed frames. (MPEG codecs) AVPacket pkt; av_init_packet(&pkt); @@ -335,3 +462,69 @@ int ffemu_finalize(ffemu_t *handle) return 0; } +static int SDLCALL ffemu_thread(void *data) +{ + ffemu_t *ff = data; + + uint16_t video_buf[512 * 448]; + int16_t audio_buf[128 * ff->params.channels]; + struct ffemu_video_data attr_buf; + + while (ff->alive) + { + bool avail_video = false; + bool avail_audio = false; + + SDL_mutexP(ff->lock); + if (fifo_read_avail(ff->attr_fifo) >= sizeof(attr_buf)) + avail_video = true; + + if (fifo_read_avail(ff->audio_fifo) >= sizeof(audio_buf)) + avail_audio = true; + SDL_mutexV(ff->lock); + + if (!avail_video && !avail_audio) + { + SDL_mutexP(ff->cond_lock); + if (ff->can_sleep) + { + ff->can_sleep = false; + SDL_CondWait(ff->cond, ff->cond_lock); + ff->can_sleep = true; + } + else + SDL_CondSignal(ff->cond); + + SDL_mutexV(ff->cond_lock); + } + + if (avail_video) + { + SDL_mutexP(ff->lock); + fifo_read(ff->attr_fifo, &attr_buf, sizeof(attr_buf)); + fifo_read(ff->video_fifo, video_buf, attr_buf.height * attr_buf.pitch); + SDL_mutexV(ff->lock); + SDL_CondSignal(ff->cond); + + attr_buf.data = video_buf; + ffemu_push_video_thread(ff, &attr_buf); + } + + if (avail_audio) + { + SDL_mutexP(ff->lock); + fifo_read(ff->audio_fifo, audio_buf, sizeof(audio_buf)); + SDL_mutexV(ff->lock); + SDL_CondSignal(ff->cond); + + struct ffemu_audio_data aud = { + .frames = 128, + .data = audio_buf + }; + + ffemu_push_audio_thread(ff, &aud); + } + } + + return 0; +} diff --git a/ssnes.c b/ssnes.c index 1156d4ae5e..01928a38e8 100644 --- a/ssnes.c +++ b/ssnes.c @@ -178,32 +178,35 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height) static void audio_sample(uint16_t left, uint16_t right) { - if ( !g_extern.audio_active ) + if (!g_extern.audio_active) + return; + + const float *output_data = NULL; + unsigned output_frames = 0; + +#ifdef HAVE_FFMPEG + g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr] = left; +#endif + g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)left/0x8000; +#ifdef HAVE_FFMPEG + g_extern.audio_data.conv_outsamples[g_extern.audio_data.data_ptr] = right; +#endif + g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)right/0x8000; + + if (g_extern.audio_data.data_ptr < g_extern.audio_data.chunk_size) return; #ifdef HAVE_FFMPEG if (g_extern.recording) { - static int16_t static_data[2]; - static_data[0] = left; - static_data[1] = right; struct ffemu_audio_data ffemu_data = { - .data = static_data, - .frames = 1 + .data = g_extern.audio_data.conv_outsamples, + .frames = g_extern.audio_data.data_ptr / 2 }; ffemu_push_audio(g_extern.rec, &ffemu_data); } #endif - const float *output_data = NULL; - unsigned output_frames = 0; - - g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)left/0x8000; - g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)right/0x8000; - - if (g_extern.audio_data.data_ptr < g_extern.audio_data.chunk_size) - return; - ssnes_dsp_input_t dsp_input = { .samples = g_extern.audio_data.data, .frames = g_extern.audio_data.data_ptr / 2