mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-12-27 08:04:14 +00:00
500282f179
This is only used for replay video recording, so the potential blast radius is minimal.
457 lines
14 KiB
Diff
457 lines
14 KiB
Diff
From 71691fad8654031328f4af077fc32aaf29cdb7d0 Mon Sep 17 00:00:00 2001
|
|
From: Pekka Ristola <pekkarr@protonmail.com>
|
|
Date: Tue, 9 May 2023 20:11:47 +0300
|
|
Subject: [PATCH] Add support for ffmpeg 6.0
|
|
|
|
- Use the new send_frame/receive_packet API for encoding
|
|
- Use the new channel layout API for audio
|
|
- Fix audio recording
|
|
- Copy codec parameters to the stream parameters
|
|
- Set correct pts for audio frames
|
|
- Read audio samples from file directly to the refcounted AVFrame buffer instead of the `g_pSamples` buffer
|
|
- Use global AVPackets allocated with `av_packet_alloc`
|
|
- Stop trying to write more audio frames when `WriteAudioFrame` fails with a negative error code
|
|
- Fix segfault with `g_pContainer->url`. The field has to be allocated with `av_malloc` before writing to it. It's set to `NULL` by default.
|
|
- Properly free allocations with `avcodec_free_context` and `avformat_free_context`
|
|
---
|
|
hedgewars/avwrapper/avwrapper.c | 234 +++++++++++++++++++++++++++-----
|
|
1 file changed, 203 insertions(+), 31 deletions(-)
|
|
|
|
diff --git a/hedgewars/avwrapper/avwrapper.c b/hedgewars/avwrapper/avwrapper.c
|
|
index 6c0fe739b4..3daeb07b75 100644
|
|
--- a/hedgewars/avwrapper/avwrapper.c
|
|
+++ b/hedgewars/avwrapper/avwrapper.c
|
|
@@ -42,15 +42,19 @@
|
|
#define UNUSED(x) (void)(x)
|
|
|
|
static AVFormatContext* g_pContainer;
|
|
-static AVOutputFormat* g_pFormat;
|
|
+static const AVOutputFormat* g_pFormat;
|
|
static AVStream* g_pAStream;
|
|
static AVStream* g_pVStream;
|
|
static AVFrame* g_pAFrame;
|
|
static AVFrame* g_pVFrame;
|
|
-static AVCodec* g_pACodec;
|
|
-static AVCodec* g_pVCodec;
|
|
+static const AVCodec* g_pACodec;
|
|
+static const AVCodec* g_pVCodec;
|
|
static AVCodecContext* g_pAudio;
|
|
static AVCodecContext* g_pVideo;
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+static AVPacket* g_pAPacket;
|
|
+static AVPacket* g_pVPacket;
|
|
+#endif
|
|
|
|
static int g_Width, g_Height;
|
|
static uint32_t g_Frequency, g_Channels;
|
|
@@ -58,8 +62,13 @@ static int g_VQuality;
|
|
static AVRational g_Framerate;
|
|
|
|
static FILE* g_pSoundFile;
|
|
+#if LIBAVUTIL_VERSION_MAJOR < 53
|
|
static int16_t* g_pSamples;
|
|
+#endif
|
|
static int g_NumSamples;
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 53
|
|
+static int64_t g_NextAudioPts;
|
|
+#endif
|
|
|
|
|
|
// compatibility section
|
|
@@ -93,6 +102,8 @@ static void rescale_ts(AVPacket *pkt, AVRational ctb, AVRational stb)
|
|
if (pkt->duration > 0)
|
|
pkt->duration = av_rescale_q(pkt->duration, ctb, stb);
|
|
}
|
|
+
|
|
+#define avcodec_free_context(ctx) do { avcodec_close(*ctx); av_freep(ctx); } while (0)
|
|
#endif
|
|
|
|
#ifndef AV_CODEC_CAP_DELAY
|
|
@@ -165,8 +176,42 @@ static void Log(const char* pFmt, ...)
|
|
AddFileLogRaw(Buffer);
|
|
}
|
|
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+static int EncodeAndWriteFrame(
|
|
+ const AVStream* pStream,
|
|
+ AVCodecContext* pCodecContext,
|
|
+ const AVFrame* pFrame,
|
|
+ AVPacket* pPacket)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = avcodec_send_frame(pCodecContext, pFrame);
|
|
+ if (ret < 0)
|
|
+ return FatalError("avcodec_send_frame failed: %d", ret);
|
|
+ while (1)
|
|
+ {
|
|
+ ret = avcodec_receive_packet(pCodecContext, pPacket);
|
|
+ if (ret == AVERROR(EAGAIN))
|
|
+ return 1;
|
|
+ else if (ret == AVERROR_EOF)
|
|
+ return 0;
|
|
+ else if (ret < 0)
|
|
+ return FatalError("avcodec_receive_packet failed: %d", ret);
|
|
+
|
|
+ av_packet_rescale_ts(pPacket, pCodecContext->time_base, pStream->time_base);
|
|
+
|
|
+ // Write the compressed frame to the media file.
|
|
+ pPacket->stream_index = pStream->index;
|
|
+ ret = av_interleaved_write_frame(g_pContainer, pPacket);
|
|
+ if (ret != 0)
|
|
+ return FatalError("Error while writing frame: %d", ret);
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
static void AddAudioStream()
|
|
{
|
|
+ int ret;
|
|
g_pAStream = avformat_new_stream(g_pContainer, g_pACodec);
|
|
if(!g_pAStream)
|
|
{
|
|
@@ -176,20 +221,44 @@ static void AddAudioStream()
|
|
g_pAStream->id = 1;
|
|
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 59
|
|
- const AVCodec *audio_st_codec = avcodec_find_decoder(g_pAStream->codecpar->codec_id);
|
|
- g_pAudio = avcodec_alloc_context3(audio_st_codec);
|
|
- avcodec_parameters_to_context(g_pAudio, g_pAStream->codecpar);
|
|
+ g_pAudio = avcodec_alloc_context3(g_pACodec);
|
|
#else
|
|
g_pAudio = g_pAStream->codec;
|
|
-#endif
|
|
|
|
avcodec_get_context_defaults3(g_pAudio, g_pACodec);
|
|
g_pAudio->codec_id = g_pACodec->id;
|
|
+#endif
|
|
|
|
// put parameters
|
|
g_pAudio->sample_fmt = AV_SAMPLE_FMT_S16;
|
|
g_pAudio->sample_rate = g_Frequency;
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 60
|
|
+ const AVChannelLayout* pChLayout = g_pACodec->ch_layouts;
|
|
+ if (pChLayout)
|
|
+ {
|
|
+ for (; pChLayout->nb_channels; pChLayout++)
|
|
+ {
|
|
+ if (pChLayout->nb_channels == g_Channels)
|
|
+ {
|
|
+ ret = av_channel_layout_copy(&g_pAudio->ch_layout, pChLayout);
|
|
+ if (ret != 0)
|
|
+ {
|
|
+ Log("Channel layout copy failed: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (!g_pAudio->ch_layout.nb_channels)
|
|
+ {
|
|
+ // no suitable layout found
|
|
+ g_pAudio->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC;
|
|
+ g_pAudio->ch_layout.nb_channels = g_Channels;
|
|
+ }
|
|
+#else
|
|
g_pAudio->channels = g_Channels;
|
|
+#endif
|
|
|
|
// set time base as invers of sample rate
|
|
g_pAudio->time_base.den = g_pAStream->time_base.den = g_Frequency;
|
|
@@ -213,6 +282,15 @@ static void AddAudioStream()
|
|
return;
|
|
}
|
|
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+ ret = avcodec_parameters_from_context(g_pAStream->codecpar, g_pAudio);
|
|
+ if (ret < 0)
|
|
+ {
|
|
+ Log("Could not copy parameters from codec context: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+#endif
|
|
+
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 54
|
|
if (g_pACodec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
|
|
#else
|
|
@@ -221,13 +299,46 @@ static void AddAudioStream()
|
|
g_NumSamples = 4096;
|
|
else
|
|
g_NumSamples = g_pAudio->frame_size;
|
|
- g_pSamples = (int16_t*)av_malloc(g_NumSamples*g_Channels*sizeof(int16_t));
|
|
g_pAFrame = av_frame_alloc();
|
|
if (!g_pAFrame)
|
|
{
|
|
Log("Could not allocate frame\n");
|
|
return;
|
|
}
|
|
+#if LIBAVUTIL_VERSION_MAJOR >= 53
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 60
|
|
+ ret = av_channel_layout_copy(&g_pAFrame->ch_layout, &g_pAudio->ch_layout);
|
|
+ if (ret != 0)
|
|
+ {
|
|
+ Log("Channel layout copy for frame failed: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+#else
|
|
+ g_pAFrame->channels = g_pAudio->channels;
|
|
+#endif
|
|
+ g_pAFrame->format = g_pAudio->sample_fmt;
|
|
+ g_pAFrame->sample_rate = g_pAudio->sample_rate;
|
|
+ g_pAFrame->nb_samples = g_NumSamples;
|
|
+ ret = av_frame_get_buffer(g_pAFrame, 1);
|
|
+ if (ret < 0)
|
|
+ {
|
|
+ Log("Failed to allocate frame buffer: %d\n", ret);
|
|
+ return;
|
|
+ }
|
|
+#else
|
|
+ g_pSamples = (int16_t*)av_malloc(g_NumSamples*g_Channels*sizeof(int16_t));
|
|
+#endif
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+ g_pAPacket = av_packet_alloc();
|
|
+ if (!g_pAPacket)
|
|
+ {
|
|
+ Log("Could not allocate audio packet\n");
|
|
+ return;
|
|
+ }
|
|
+#endif
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 53
|
|
+ g_NextAudioPts = 0;
|
|
+#endif
|
|
}
|
|
|
|
// returns non-zero if there is more sound, -1 in case of error
|
|
@@ -236,22 +347,46 @@ static int WriteAudioFrame()
|
|
if (!g_pAStream)
|
|
return 0;
|
|
|
|
- AVPacket Packet;
|
|
- av_init_packet(&Packet);
|
|
- Packet.data = NULL;
|
|
- Packet.size = 0;
|
|
+ int ret;
|
|
+ int16_t* pData;
|
|
+#if LIBAVUTIL_VERSION_MAJOR >= 53
|
|
+ ret = av_frame_make_writable(g_pAFrame);
|
|
+ if (ret < 0)
|
|
+ return FatalError("Could not make audio frame writable: %d", ret);
|
|
+ pData = (int16_t*) g_pAFrame->data[0];
|
|
+#else
|
|
+ pData = g_pSamples;
|
|
+#endif
|
|
|
|
- int NumSamples = fread(g_pSamples, 2*g_Channels, g_NumSamples, g_pSoundFile);
|
|
+ int NumSamples = fread(pData, 2*g_Channels, g_NumSamples, g_pSoundFile);
|
|
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 53
|
|
AVFrame* pFrame = NULL;
|
|
if (NumSamples > 0)
|
|
{
|
|
g_pAFrame->nb_samples = NumSamples;
|
|
+ g_pAFrame->pts = g_NextAudioPts;
|
|
+ g_NextAudioPts += NumSamples;
|
|
+#if LIBAVUTIL_VERSION_MAJOR < 53
|
|
avcodec_fill_audio_frame(g_pAFrame, g_Channels, AV_SAMPLE_FMT_S16,
|
|
- (uint8_t*)g_pSamples, NumSamples*2*g_Channels, 1);
|
|
+ (uint8_t*)pData, NumSamples*2*g_Channels, 1);
|
|
+#endif
|
|
pFrame = g_pAFrame;
|
|
}
|
|
+#endif
|
|
+
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+ ret = EncodeAndWriteFrame(g_pAStream, g_pAudio, pFrame, g_pAPacket);
|
|
+ if (ret < 0)
|
|
+ return FatalError("Audio frame processing failed");
|
|
+ return ret;
|
|
+#else
|
|
+ AVPacket Packet;
|
|
+ av_init_packet(&Packet);
|
|
+ Packet.data = NULL;
|
|
+ Packet.size = 0;
|
|
+
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 53
|
|
// when NumSamples == 0 we still need to call encode_audio2 to flush
|
|
int got_packet;
|
|
if (avcodec_encode_audio2(g_pAudio, &Packet, pFrame, &got_packet) != 0)
|
|
@@ -266,7 +401,7 @@ static int WriteAudioFrame()
|
|
int BufferSize = OUTBUFFER_SIZE;
|
|
if (g_pAudio->frame_size == 0)
|
|
BufferSize = NumSamples*g_Channels*2;
|
|
- Packet.size = avcodec_encode_audio(g_pAudio, g_OutBuffer, BufferSize, g_pSamples);
|
|
+ Packet.size = avcodec_encode_audio(g_pAudio, g_OutBuffer, BufferSize, pData);
|
|
if (Packet.size == 0)
|
|
return 1;
|
|
if (g_pAudio->coded_frame && g_pAudio->coded_frame->pts != AV_NOPTS_VALUE)
|
|
@@ -280,25 +415,25 @@ static int WriteAudioFrame()
|
|
if (av_interleaved_write_frame(g_pContainer, &Packet) != 0)
|
|
return FatalError("Error while writing audio frame");
|
|
return 1;
|
|
+#endif
|
|
}
|
|
|
|
// add a video output stream
|
|
static int AddVideoStream()
|
|
{
|
|
+ int ret;
|
|
g_pVStream = avformat_new_stream(g_pContainer, g_pVCodec);
|
|
if (!g_pVStream)
|
|
return FatalError("Could not allocate video stream");
|
|
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 59
|
|
- const AVCodec *video_st_codec = avcodec_find_decoder(g_pVStream->codecpar->codec_id);
|
|
- g_pVideo = avcodec_alloc_context3(video_st_codec);
|
|
- avcodec_parameters_to_context(g_pVideo, g_pVStream->codecpar);
|
|
+ g_pVideo = avcodec_alloc_context3(g_pVCodec);
|
|
#else
|
|
g_pVideo = g_pVStream->codec;
|
|
-#endif
|
|
|
|
avcodec_get_context_defaults3(g_pVideo, g_pVCodec);
|
|
g_pVideo->codec_id = g_pVCodec->id;
|
|
+#endif
|
|
|
|
// put parameters
|
|
// resolution must be a multiple of two
|
|
@@ -361,6 +496,12 @@ static int AddVideoStream()
|
|
if (avcodec_open2(g_pVideo, g_pVCodec, NULL) < 0)
|
|
return FatalError("Could not open video codec %s", g_pVCodec->long_name);
|
|
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+ ret = avcodec_parameters_from_context(g_pVStream->codecpar, g_pVideo);
|
|
+ if (ret < 0)
|
|
+ return FatalError("Could not copy parameters from codec context: %d", ret);
|
|
+#endif
|
|
+
|
|
g_pVFrame = av_frame_alloc();
|
|
if (!g_pVFrame)
|
|
return FatalError("Could not allocate frame");
|
|
@@ -370,6 +511,12 @@ static int AddVideoStream()
|
|
g_pVFrame->height = g_Height;
|
|
g_pVFrame->format = AV_PIX_FMT_YUV420P;
|
|
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+ g_pVPacket = av_packet_alloc();
|
|
+ if (!g_pVPacket)
|
|
+ return FatalError("Could not allocate packet");
|
|
+#endif
|
|
+
|
|
return avcodec_default_get_buffer2(g_pVideo, g_pVFrame, 0);
|
|
}
|
|
|
|
@@ -380,6 +527,10 @@ static int WriteFrame(AVFrame* pFrame)
|
|
// write interleaved audio frame
|
|
if (g_pAStream)
|
|
{
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+ if (!g_pAPacket)
|
|
+ return FatalError("Error while writing video frame: g_pAPacket does not exist");
|
|
+#endif
|
|
VideoTime = (double)g_pVFrame->pts * g_pVStream->time_base.num/g_pVStream->time_base.den;
|
|
do
|
|
{
|
|
@@ -388,7 +539,7 @@ static int WriteFrame(AVFrame* pFrame)
|
|
AudioTime = (double)g_pAFrame->pts * g_pAStream->time_base.num/g_pAStream->time_base.den;
|
|
ret = WriteAudioFrame();
|
|
}
|
|
- while (AudioTime < VideoTime && ret);
|
|
+ while (AudioTime < VideoTime && ret > 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
@@ -396,13 +547,18 @@ static int WriteFrame(AVFrame* pFrame)
|
|
if (!g_pVStream)
|
|
return 0;
|
|
|
|
+ g_pVFrame->pts++;
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+ ret = EncodeAndWriteFrame(g_pVStream, g_pVideo, pFrame, g_pVPacket);
|
|
+ if (ret < 0)
|
|
+ return FatalError("Video frame processing failed");
|
|
+ return ret;
|
|
+#else
|
|
AVPacket Packet;
|
|
av_init_packet(&Packet);
|
|
Packet.data = NULL;
|
|
Packet.size = 0;
|
|
|
|
- g_pVFrame->pts++;
|
|
-#if LIBAVCODEC_VERSION_MAJOR < 58
|
|
if (g_pFormat->flags & AVFMT_RAWPICTURE)
|
|
{
|
|
/* raw video case. The API will change slightly in the near
|
|
@@ -417,7 +573,6 @@ static int WriteFrame(AVFrame* pFrame)
|
|
return 0;
|
|
}
|
|
else
|
|
-#endif
|
|
{
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 54
|
|
int got_packet;
|
|
@@ -447,6 +602,7 @@ static int WriteFrame(AVFrame* pFrame)
|
|
|
|
return 1;
|
|
}
|
|
+#endif
|
|
}
|
|
|
|
AVWRAP_DECL int AVWrapper_WriteFrame(uint8_t *buf)
|
|
@@ -539,9 +695,13 @@ AVWRAP_DECL int AVWrapper_Init(
|
|
char ext[16];
|
|
strncpy(ext, g_pFormat->extensions, 16);
|
|
ext[15] = 0;
|
|
- ext[strcspn(ext,",")] = 0;
|
|
+ size_t extLen = strcspn(ext, ",");
|
|
+ ext[extLen] = 0;
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 59
|
|
- snprintf(g_pContainer->url, sizeof(g_pContainer->url), "%s.%s", pFilename, ext);
|
|
+ // pFilename + dot + ext + null byte
|
|
+ size_t urlLen = strlen(pFilename) + 1 + extLen + 1;
|
|
+ g_pContainer->url = av_malloc(urlLen);
|
|
+ snprintf(g_pContainer->url, urlLen, "%s.%s", pFilename, ext);
|
|
#else
|
|
snprintf(g_pContainer->filename, sizeof(g_pContainer->filename), "%s.%s", pFilename, ext);
|
|
#endif
|
|
@@ -636,21 +796,33 @@ AVWRAP_DECL int AVWrapper_Close()
|
|
// free everything
|
|
if (g_pVStream)
|
|
{
|
|
- avcodec_close(g_pVideo);
|
|
- av_free(g_pVideo);
|
|
- av_free(g_pVStream);
|
|
+ avcodec_free_context(&g_pVideo);
|
|
av_frame_free(&g_pVFrame);
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+ av_packet_free(&g_pVPacket);
|
|
+#endif
|
|
}
|
|
if (g_pAStream)
|
|
{
|
|
- avcodec_close(g_pAudio);
|
|
- av_free(g_pAudio);
|
|
- av_free(g_pAStream);
|
|
+ avcodec_free_context(&g_pAudio);
|
|
av_frame_free(&g_pAFrame);
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 58
|
|
+ av_packet_free(&g_pAPacket);
|
|
+#endif
|
|
+#if LIBAVUTIL_VERSION_MAJOR < 53
|
|
av_free(g_pSamples);
|
|
+#endif
|
|
fclose(g_pSoundFile);
|
|
}
|
|
|
|
+#if LIBAVCODEC_VERSION_MAJOR >= 59
|
|
+ avformat_free_context(g_pContainer);
|
|
+#else
|
|
+ if (g_pVStream)
|
|
+ av_free(g_pVStream);
|
|
+ if (g_pAStream)
|
|
+ av_free(g_pAStream);
|
|
av_free(g_pContainer);
|
|
+#endif
|
|
return 0;
|
|
}
|