hedgewars: add patch for FFmpeg ≥ 6

This is only used for replay video recording, so the potential blast
radius is minimal.
This commit is contained in:
Emily 2024-07-13 11:01:59 +01:00
parent 9e94196358
commit 500282f179
2 changed files with 464 additions and 2 deletions

View File

@ -1,4 +1,4 @@
{ stdenv, SDL2_image_2_6, SDL2_ttf, SDL2_net, fpc, haskell, ffmpeg_4, libglut
{ stdenv, SDL2_image_2_6, SDL2_ttf, SDL2_net, fpc, haskell, ffmpeg_7, libglut
, lib, fetchurl, cmake, pkg-config, lua5_1, SDL2, SDL2_mixer
, zlib, libpng, libGL, libGLU, physfs
, qtbase, qttools, wrapQtAppsHook
@ -21,13 +21,19 @@ stdenv.mkDerivation rec {
sha256 = "sha256-IB/l5FvYyls9gbGOwGvWu8n6fCxjvwGQBeL4C+W88hI=";
};
patches = [
# Add support for ffmpeg 6.0
# https://github.com/hedgewars/hw/pull/74
./support-ffmpeg-6.patch
];
nativeBuildInputs = [ cmake pkg-config qttools wrapQtAppsHook ];
buildInputs = [
SDL2_ttf SDL2_net SDL2 SDL2_mixer SDL2_image_2_6
fpc lua5_1
llvm # hard-requirement on aarch64, for some reason not strictly necessary on x86-64
ffmpeg_4 libglut physfs
ffmpeg_7 libglut physfs
qtbase
] ++ lib.optional withServer ghc;

View File

@ -0,0 +1,456 @@
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;
}