diff --git a/Source_Files/FFmpeg/Movie.cpp b/Source_Files/FFmpeg/Movie.cpp index d78b0a88..c95bfe27 100644 --- a/Source_Files/FFmpeg/Movie.cpp +++ b/Source_Files/FFmpeg/Movie.cpp @@ -114,10 +114,27 @@ static int get_cpu_count(void) return cpu_count; } +#define AV_FIFO_BUFFER_SIZE (1<<18) +#if USE_NEW_AV_FIFO_API + #define AV_FIFO_POLL_DELAY_MS 1 + #define AV_FIFO_MAX_WAIT_MS 10 + + #ifdef __WIN32__ + #define SleepForMlliseconds Sleep + #else + #include + #define SleepForMilliseconds(X) usleep(1000 * (X)) + #endif +#endif + struct libav_vars { bool inited; +#if USE_NEW_AV_FIFO_API + AVFifo *audio_fifo; +#else AVFifoBuffer *audio_fifo; +#endif SDL_ffmpegFile* ffmpeg_file; SDL_ffmpegAudioFrame* audio_frame; @@ -279,7 +296,11 @@ bool Movie::Setup() if (avformat_write_header(av->ffmpeg_file->_ffmpeg, 0) < 0) { ThrowUserError("Could not write header"); return false; } - av->audio_fifo = av_fifo_alloc(262144); +#if USE_NEW_AV_FIFO_API + av->audio_fifo = av_fifo_alloc2(AV_FIFO_BUFFER_SIZE / AV_FIFO_CHUNK_SIZE, AV_FIFO_CHUNK_SIZE, 0); +#else + av->audio_fifo = av_fifo_alloc(AV_FIFO_BUFFER_SIZE); +#endif if (!av->audio_fifo) { ThrowUserError("Could not allocate audio fifo"); return false; } // set up our threads and intermediate storage @@ -335,18 +356,56 @@ void Movie::EncodeVideo(bool last) void Movie::EncodeAudio(bool last) { - av_fifo_generic_write(av->audio_fifo, &audiobuf[0], audiobuf.size(), NULL); +#if USE_NEW_AV_FIFO_API + size_t max_write = audiobuf.size(); + size_t written_so_far = 0, sleep_counter = 0; + + while (written_so_far < max_write) { + int writable = MAX(av_fifo_can_write(av->audio_fifo) * AV_FIFO_CHUNK_SIZE, max_write - written_so_far); + + if (!writable) { + if (sleep_counter * AV_FIFO_POLL_DELAY_MS >= AV_FIFO_MAX_WAIT_MS) + break; + SleepForMilliseconds(AV_FIFO_POLL_DELAY_MS); + sleep_counter++; + } else { + if (av_fifo_write(av->audio_fifo, &audiobuf[0], writable) < 0) + break; + written_so_far += writable; + sleep_counter = 0; + } + } +#else + av_fifo_generic_write(av->audio_fifo, &audiobuf[0], audiobuf.size(), NULL); +#endif + auto acodec = av->ffmpeg_file->audioStream->_ctx; // bps: bytes per sample +#if USE_NEW_AV_FIFO_API + int channels = acodec->ch_layout.nb_channels; +#else int channels = acodec->channels; +#endif - int max_read = acodec->frame_size * in_bps * channels; - int min_read = last ? in_bps * channels : max_read; + size_t max_read = acodec->frame_size * in_bps * channels; + size_t min_read = last ? in_bps * channels : max_read; + +#if USE_NEW_AV_FIFO_API + size_t read_so_far = 0; + while (read_so_far < max_read && av_fifo_can_read(av->audio_fifo) >= min_read) + { + int read_bytes = av->audio_frame->size = MIN(AV_FIFO_CHUNK_SIZE * av_fifo_can_read(av->audio_fifo), max_read); + + if (av_fifo_read(av->audio_fifo, av->audio_frame->buffer, read_bytes) < 0) + break; + read_so_far += read_bytes; +#else while (av_fifo_size(av->audio_fifo) >= min_read) { int read_bytes = av->audio_frame->size = MIN(av_fifo_size(av->audio_fifo), max_read); av_fifo_generic_read(av->audio_fifo, av->audio_frame->buffer, read_bytes, NULL); +#endif SDL_ffmpegAddAudioFrame(av->ffmpeg_file, av->audio_frame, &av->audio_counter, last); } } @@ -466,8 +525,12 @@ void Movie::StopRecording() } if (av->audio_fifo) { +#if USE_NEW_AV_FIFO_API + av_fifo_freep2(&av->audio_fifo); +#else av_fifo_free(av->audio_fifo); - av->audio_fifo = NULL; + av->audio_fifo = NULL; +#endif } moviefile = ""; diff --git a/Source_Files/FFmpeg/SDL_ffmpeg.c b/Source_Files/FFmpeg/SDL_ffmpeg.c index 73740e25..eb6322bc 100644 --- a/Source_Files/FFmpeg/SDL_ffmpeg.c +++ b/Source_Files/FFmpeg/SDL_ffmpeg.c @@ -444,6 +444,19 @@ SDL_ffmpegFile* SDL_ffmpegOpen( const char* filename ) } else { + int rv = 0; +#if USE_NEW_AV_FIFO_API + int channel_layout = stream->_ffmpeg->codecpar->ch_layout.u.mask ? stream->_ffmpeg->codecpar->ch_layout.u.mask : + (stream->_ffmpeg->codecpar->ch_layout.nb_channels == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO); + AVChannelLayout in_layout, out_layout; + + in_layout = out_layout = stream->_ffmpeg->codecpar->ch_layout; + in_layout.u.mask = out_layout.u.mask = channel_layout; + rv = swr_alloc_set_opts2(&stream->swr_context, &out_layout, AV_SAMPLE_FMT_FLT, + stream->_ffmpeg->codecpar->sample_rate, &in_layout, + stream->_ffmpeg->codecpar->format, stream->_ffmpeg->codecpar->sample_rate, + 0, NULL); +#else int channel_layout = stream->_ffmpeg->codecpar->channel_layout ? stream->_ffmpeg->codecpar->channel_layout : (stream->_ffmpeg->codecpar->channels == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO); @@ -451,8 +464,9 @@ SDL_ffmpegFile* SDL_ffmpegOpen( const char* filename ) stream->_ffmpeg->codecpar->sample_rate, channel_layout, stream->_ffmpeg->codecpar->format, stream->_ffmpeg->codecpar->sample_rate, 0, NULL); +#endif - if (!stream->swr_context || swr_init(stream->swr_context) < 0) { + if (rv < 0 || !stream->swr_context || swr_init(stream->swr_context) < 0) { free(stream); SDL_ffmpegSetError("could not initialize resampler"); continue; @@ -648,7 +662,11 @@ int SDL_ffmpegAddAudioFrame( SDL_ffmpegFile *file, SDL_ffmpegAudioFrame *frame, // convert int32_t write_bps = av_get_bytes_per_sample(acodec->sample_fmt); +#if USE_NEW_AV_FIFO_API + int32_t read_samples = frame->size / (av_get_bytes_per_sample(file->audioStream->audioFormat) * acodec->ch_layout.nb_channels); +#else int32_t read_samples = frame->size / (av_get_bytes_per_sample(file->audioStream->audioFormat) * acodec->channels); +#endif int32_t write_samples = read_samples; if (read_samples < acodec->frame_size) { @@ -665,9 +683,14 @@ int SDL_ffmpegAddAudioFrame( SDL_ffmpegFile *file, SDL_ffmpegAudioFrame *frame, av_frame_unref(audio_frame); //Needed since ffmpeg 4.4 +#if USE_NEW_AV_FIFO_API + audio_frame->ch_layout.nb_channels = acodec->ch_layout.nb_channels; + audio_frame->ch_layout.u.mask = acodec->ch_layout.u.mask; +#else audio_frame->channels = acodec->channels; - audio_frame->format = acodec->sample_fmt; audio_frame->channel_layout = acodec->channel_layout; +#endif + audio_frame->format = acodec->sample_fmt; audio_frame->sample_rate = acodec->sample_rate; audio_frame->nb_samples = write_samples; @@ -675,10 +698,17 @@ int SDL_ffmpegAddAudioFrame( SDL_ffmpegFile *file, SDL_ffmpegAudioFrame *frame, audio_frame->pts = av_rescale_q(*frameCounter, avSampleRate, acodec->time_base); *frameCounter += write_samples; +#if USE_NEW_AV_FIFO_API + int asize = avcodec_fill_audio_frame(audio_frame, acodec->ch_layout.nb_channels, + acodec->sample_fmt, + frame->conversionBuffer[0], + write_samples * write_bps * acodec->ch_layout.nb_channels, 1); +#else int asize = avcodec_fill_audio_frame(audio_frame, acodec->channels, acodec->sample_fmt, frame->conversionBuffer[0], write_samples * write_bps * acodec->channels, 1); +#endif if (asize >= 0) { @@ -762,10 +792,18 @@ SDL_ffmpegAudioFrame* SDL_ffmpegCreateAudioFrame( SDL_ffmpegFile *file, uint32_t if ( file->type == SDL_ffmpegOutputStream ) { +#if USE_NEW_AV_FIFO_API + bytes = file->audioStream->encodeAudioInputSize * av_get_bytes_per_sample(file->audioStream->audioFormat) * file->audioStream->_ctx->ch_layout.nb_channels; +#else bytes = file->audioStream->encodeAudioInputSize * av_get_bytes_per_sample(file->audioStream->audioFormat) * file->audioStream->_ctx->channels; +#endif // allocate conversion buffer only when output, input does it differently +#if USE_NEW_AV_FIFO_API + if (av_samples_alloc_array_and_samples(&frame->conversionBuffer, NULL, file->audioStream->_ctx->ch_layout.nb_channels, file->audioStream->encodeAudioInputSize, file->audioStream->_ctx->sample_fmt, 0) < 0) +#else if (av_samples_alloc_array_and_samples(&frame->conversionBuffer, NULL, file->audioStream->_ctx->channels, file->audioStream->encodeAudioInputSize, file->audioStream->_ctx->sample_fmt, 0) < 0) +#endif { return 0; } @@ -1355,7 +1393,11 @@ SDL_AudioSpec SDL_ffmpegGetAudioSpec( SDL_ffmpegFile *file, uint16_t samples, SD spec.userdata = file; spec.callback = callback; spec.freq = file->audioStream->_ctx->sample_rate; +#if USE_NEW_AV_FIFO_API + spec.channels = ( uint8_t )file->audioStream->_ctx->ch_layout.nb_channels; +#else spec.channels = ( uint8_t )file->audioStream->_ctx->channels; +#endif } else { @@ -1687,8 +1729,13 @@ SDL_ffmpegStream* SDL_ffmpegAddAudioStream( SDL_ffmpegFile *file, SDL_ffmpegCode stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; stream->codecpar->sample_rate = codec.sampleRate; stream->codecpar->format = AV_SAMPLE_FMT_FLTP; +#if USE_NEW_AV_FIFO_API + stream->codecpar->ch_layout.nb_channels = codec.channels; + stream->codecpar->ch_layout.u.mask = codec.channels == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; +#else stream->codecpar->channels = codec.channels; stream->codecpar->channel_layout = codec.channels == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; +#endif if (avcodec_parameters_to_context(context, stream->codecpar) < 0) { @@ -1721,6 +1768,11 @@ SDL_ffmpegStream* SDL_ffmpegAddAudioStream( SDL_ffmpegFile *file, SDL_ffmpegCode if ( str ) { +#if USE_NEW_AV_FIFO_API + AVChannelLayout in_layout, out_layout; +#endif + int rv = 0; + /* we set our stream to zero */ memset( str, 0, sizeof( SDL_ffmpegStream ) ); @@ -1734,10 +1786,16 @@ SDL_ffmpegStream* SDL_ffmpegAddAudioStream( SDL_ffmpegFile *file, SDL_ffmpegCode str->audioFormat = codec.audioFormat; // init resampler +#if USE_NEW_AV_FIFO_API + in_layout = out_layout = context->ch_layout; + rv = swr_alloc_set_opts2(&str->swr_context, &out_layout, context->sample_fmt, context->sample_rate, + &in_layout, str->audioFormat, context->sample_rate, 0, NULL); +#else str->swr_context = swr_alloc_set_opts(str->swr_context, context->channel_layout, context->sample_fmt, context->sample_rate, context->channel_layout, str->audioFormat, context->sample_rate, 0, NULL); +#endif - if (!str->swr_context || swr_init(str->swr_context) < 0) + if (rv < 0 || !str->swr_context || swr_init(str->swr_context) < 0) { SDL_ffmpegSetError("could not initialize resampler"); return 0; @@ -1745,9 +1803,15 @@ SDL_ffmpegStream* SDL_ffmpegAddAudioStream( SDL_ffmpegFile *file, SDL_ffmpegCode str->mutex = SDL_CreateMutex(); +#if USE_NEW_AV_FIFO_API + str->sampleBufferSize = av_samples_get_buffer_size(0, stream->codecpar->ch_layout.nb_channels, stream->codecpar->frame_size, AV_SAMPLE_FMT_FLT, 0); + + if (av_samples_alloc((uint8_t**)(&str->sampleBuffer), 0, stream->codecpar->ch_layout.nb_channels, stream->codecpar->frame_size, AV_SAMPLE_FMT_FLT, 0) < 0) +#else str->sampleBufferSize = av_samples_get_buffer_size(0, stream->codecpar->channels, stream->codecpar->frame_size, AV_SAMPLE_FMT_FLT, 0); if (av_samples_alloc((uint8_t**)(&str->sampleBuffer), 0, stream->codecpar->channels, stream->codecpar->frame_size, AV_SAMPLE_FMT_FLT, 0) < 0) +#endif { SDL_ffmpegSetError("could not allocate samples for audio buffer"); return 0; @@ -1757,7 +1821,11 @@ SDL_ffmpegStream* SDL_ffmpegAddAudioStream( SDL_ffmpegFile *file, SDL_ffmpegCode support to compute the input frame size in samples */ if ( stream->codecpar->frame_size <= 1 ) { +#if USE_NEW_AV_FIFO_API + str->encodeAudioInputSize = str->sampleBufferSize / stream->codecpar->ch_layout.nb_channels; +#else str->encodeAudioInputSize = str->sampleBufferSize / stream->codecpar->channels; +#endif switch ( stream->codecpar->codec_id ) { @@ -1946,7 +2014,11 @@ int SDL_ffmpegDecodeAudioFrame( SDL_ffmpegFile *file, AVPacket *pack, SDL_ffmpeg { int audioSize = AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof( float ); +#if USE_NEW_AV_FIFO_API + int channels = file->audioStream->_ctx->ch_layout.nb_channels; +#else int channels = file->audioStream->_ctx->channels; +#endif enum AVSampleFormat format = AV_SAMPLE_FMT_FLT; int bps = av_get_bytes_per_sample(format); @@ -2020,10 +2092,14 @@ int SDL_ffmpegDecodeAudioFrame( SDL_ffmpegFile *file, AVPacket *pack, SDL_ffmpeg AVFrame* convertedFrame = file->audioStream->encodeFrame; while (avcodec_receive_frame(avctx, dframe) == 0) { - +#if USE_NEW_AV_FIFO_API + dframe->ch_layout.u.mask |= dframe->ch_layout.nb_channels == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; + convertedFrame->ch_layout.u.mask = dframe->ch_layout.u.mask; +#else dframe->channel_layout |= dframe->channels == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; - convertedFrame->nb_samples = dframe->nb_samples; convertedFrame->channel_layout = dframe->channel_layout; +#endif + convertedFrame->nb_samples = dframe->nb_samples; convertedFrame->sample_rate = dframe->sample_rate; convertedFrame->format = AV_SAMPLE_FMT_FLT; @@ -2036,15 +2112,27 @@ int SDL_ffmpegDecodeAudioFrame( SDL_ffmpegFile *file, AVPacket *pack, SDL_ffmpeg int planar = av_sample_fmt_is_planar(convertedFrame->format); int plane_size; +#if USE_NEW_AV_FIFO_API + int data_size = av_samples_get_buffer_size(&plane_size, convertedFrame->ch_layout.nb_channels, convertedFrame->nb_samples, convertedFrame->format, 1); +#else int data_size = av_samples_get_buffer_size(&plane_size, convertedFrame->channels, convertedFrame->nb_samples, convertedFrame->format, 1); +#endif memcpy(file->audioStream->sampleBuffer, convertedFrame->extended_data[0], plane_size); audioSize = plane_size; +#if USE_NEW_AV_FIFO_API + if (planar && convertedFrame->ch_layout.nb_channels > 1) +#else if (planar && convertedFrame->channels > 1) +#endif { int8_t* out = file->audioStream->sampleBuffer + plane_size; int ch; +#if USE_NEW_AV_FIFO_API + for (ch = 1; ch < convertedFrame->ch_layout.nb_channels; ch++) +#else for (ch = 1; ch < convertedFrame->channels; ch++) +#endif { memcpy(out, convertedFrame->extended_data[ch], plane_size); out += plane_size; diff --git a/Source_Files/FFmpeg/SDL_ffmpeg.h b/Source_Files/FFmpeg/SDL_ffmpeg.h index 26d5c92b..253e48a5 100644 --- a/Source_Files/FFmpeg/SDL_ffmpeg.h +++ b/Source_Files/FFmpeg/SDL_ffmpeg.h @@ -40,6 +40,14 @@ extern "C" { #define EXPORT // #endif +// The avutil FIFO API underwent major changes in version 57.20.100 +// (cf. https://git.videolan.org/?p=ffmpeg.git;a=blob_plain;f=doc/APIchanges;hb=n7.0 ) +#include +#if LIBAVUTIL_VERSION_MAJOR > 57 || LIBAVUTIL_VERSION_MAJOR == 57 && LIBAVUTIL_VERSION_MINOR >= 20 + #define USE_NEW_AV_FIFO_API 1 + #define AV_FIFO_CHUNK_SIZE 4 +#endif + enum SDL_ffmpegStreamType { SDL_ffmpegUninitialized = 0, diff --git a/Source_Files/Sound/FFmpegDecoder.cpp b/Source_Files/Sound/FFmpegDecoder.cpp index a166155b..a97c405b 100644 --- a/Source_Files/Sound/FFmpegDecoder.cpp +++ b/Source_Files/Sound/FFmpegDecoder.cpp @@ -50,10 +50,16 @@ extern "C" } #endif +#define AV_FIFO_BUFFER_SIZE (1<<19) + struct ffmpeg_vars { SDL_ffmpegFile* file; SDL_ffmpegAudioFrame* frame; +#if USE_NEW_AV_FIFO_API + AVFifo *fifo; +#else AVFifoBuffer *fifo; +#endif bool started; }; typedef struct ffmpeg_vars ffmpeg_vars_t; @@ -64,14 +70,22 @@ FFmpegDecoder::FFmpegDecoder() : av = new ffmpeg_vars_t; memset(av, 0, sizeof(ffmpeg_vars_t)); - av->fifo = av_fifo_alloc(524288); +#if USE_NEW_AV_FIFO_API + av->fifo = av_fifo_alloc2(AV_FIFO_BUFFER_SIZE / AV_FIFO_CHUNK_SIZE, AV_FIFO_CHUNK_SIZE, 0); +#else + av->fifo = av_fifo_alloc(AV_FIFO_BUFFER_SIZE); +#endif } FFmpegDecoder::~FFmpegDecoder() { Close(); if (av && av->fifo) +#if USE_NEW_AV_FIFO_API + av_fifo_freep2(&av->fifo); +#else av_fifo_free(av->fifo); +#endif } bool FFmpegDecoder::Open(FileSpecifier& File) @@ -99,30 +113,47 @@ bool FFmpegDecoder::Open(FileSpecifier& File) return false; } +#if USE_NEW_AV_FIFO_API + channels = av->file->audioStream->_ffmpeg->codecpar->ch_layout.nb_channels; +#else channels = av->file->audioStream->_ffmpeg->codecpar->channels; +#endif rate = av->file->audioStream->_ffmpeg->codecpar->sample_rate; return true; } int32 FFmpegDecoder::Decode(uint8* buffer, int32 max_length) { - int32 total_bytes_read = 0; - uint8* cur = buffer; + size_t total_bytes_read = 0; + while (total_bytes_read < max_length) { +#if USE_NEW_AV_FIFO_API + size_t fifo_chunks_waiting = av_fifo_can_read(av->fifo); + if (!fifo_chunks_waiting) + { + if (!GetAudio()) + break; + fifo_chunks_waiting = av_fifo_can_read(av->fifo); + } + size_t chunks_to_read = std::min(fifo_chunks_waiting * AV_FIFO_CHUNK_SIZE, (max_length - total_bytes_read + AV_FIFO_CHUNK_SIZE - 1) / AV_FIFO_CHUNK_SIZE); + if (!chunks_to_read || av_fifo_read(av->fifo, buffer + total_bytes_read, chunks_to_read) < 0) + break; + total_bytes_read += chunks_to_read * AV_FIFO_CHUNK_SIZE; +#else int32 fifo_size = av_fifo_size(av->fifo); if (!fifo_size) { if (!GetAudio()) break; fifo_size = av_fifo_size(av->fifo); - } - int bytes_read = std::min(fifo_size, max_length - total_bytes_read); - av_fifo_generic_read(av->fifo, cur, bytes_read, NULL); + } + int bytes_read = std::min(fifo_size, max_length - (int) total_bytes_read); + av_fifo_generic_read(av->fifo, buffer + total_bytes_read, bytes_read, NULL); total_bytes_read += bytes_read; - cur += bytes_read; +#endif } - + memset(&buffer[total_bytes_read], 0, max_length - total_bytes_read); return total_bytes_read; } @@ -132,7 +163,11 @@ void FFmpegDecoder::Rewind() if (av->started) { SDL_ffmpegSeekRelative(av->file, 0); +#if USE_NEW_AV_FIFO_API + av_fifo_reset2(av->fifo); +#else av_fifo_reset(av->fifo); +#endif av->started = false; } } @@ -144,15 +179,32 @@ void FFmpegDecoder::Close() if (av && av->frame) SDL_ffmpegFreeAudioFrame(av->frame); if (av && av->fifo) +#if USE_NEW_AV_FIFO_API + av_fifo_reset2(av->fifo); +#else av_fifo_reset(av->fifo); +#endif if (av) av->started = false; } bool FFmpegDecoder::GetAudio() { - if (!SDL_ffmpegGetAudioFrame(av->file, av->frame)) return false; + if (!SDL_ffmpegGetAudioFrame(av->file, av->frame)) + return false; +#if USE_NEW_AV_FIFO_API + for (size_t bytes_written = 0; bytes_written < av->frame->size; ) + { + size_t free_chunks_in_fifo = av_fifo_can_write(av->fifo); + size_t chunks_to_write = std::min((av->frame->size - bytes_written + AV_FIFO_CHUNK_SIZE - 1) / AV_FIFO_CHUNK_SIZE, free_chunks_in_fifo); + if (!free_chunks_in_fifo || av_fifo_write(av->fifo, av->frame->buffer + bytes_written, chunks_to_write) < 0) + break; + bytes_written += chunks_to_write * AV_FIFO_CHUNK_SIZE; + } +#else av_fifo_generic_write(av->fifo, av->frame->buffer, av->frame->size, NULL); +#endif + av->started = true; return true; }