See https://github.com/cmus/cmus/pull/1444 OBS-URL: https://build.opensuse.org/package/show/multimedia:apps/cmus?expand=0&rev=42
1874 lines
58 KiB
Diff
1874 lines
58 KiB
Diff
From 3d1f50e74bc46af42a35771ccae2225826ebfed3 Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Thu, 14 Aug 2025 12:44:10 +0300
|
|
Subject: [PATCH 01/13] ip/ffmpeg: more precise seeking
|
|
|
|
av_seek_frame() and avformat_seek_file() seek to nearest "keyframe". For
|
|
codecs like, for example, ape this means that seeking will be very off
|
|
(5 seconds or more). So what we do is:
|
|
1. seek to nearest "keyframe" before the desired time,
|
|
2. discard some frames to approach the desired time.
|
|
---
|
|
ip/ffmpeg.c | 154 ++++++++++++++++++++++++++++++++--------------------
|
|
1 file changed, 94 insertions(+), 60 deletions(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index 21b9a01f4..ecbf00582 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -44,6 +44,8 @@ struct ffmpeg_input {
|
|
AVPacket pkt;
|
|
int curr_pkt_size;
|
|
uint8_t *curr_pkt_buf;
|
|
+ int64_t seek_ts;
|
|
+ int64_t prev_frame_end;
|
|
int stream_index;
|
|
|
|
unsigned long curr_size;
|
|
@@ -76,6 +78,8 @@ static struct ffmpeg_input *ffmpeg_input_create(void)
|
|
return NULL;
|
|
}
|
|
input->curr_pkt_size = 0;
|
|
+ input->seek_ts = -1;
|
|
+ input->prev_frame_end = -1;
|
|
input->curr_pkt_buf = input->pkt.data;
|
|
return input;
|
|
}
|
|
@@ -314,10 +318,7 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
|
|
#else
|
|
AVFrame *frame = avcodec_alloc_frame();
|
|
#endif
|
|
- int got_frame;
|
|
while (1) {
|
|
- int len;
|
|
-
|
|
if (input->curr_pkt_size <= 0) {
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 56
|
|
av_packet_unref(&input->pkt);
|
|
@@ -333,78 +334,108 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
|
|
#endif
|
|
return 0;
|
|
}
|
|
- if (input->pkt.stream_index == input->stream_index) {
|
|
- input->curr_pkt_size = input->pkt.size;
|
|
- input->curr_pkt_buf = input->pkt.data;
|
|
- input->curr_size += input->pkt.size;
|
|
- input->curr_duration += input->pkt.duration;
|
|
- }
|
|
- continue;
|
|
- }
|
|
|
|
- {
|
|
- AVPacket avpkt;
|
|
- av_new_packet(&avpkt, input->curr_pkt_size);
|
|
- memcpy(avpkt.data, input->curr_pkt_buf, input->curr_pkt_size);
|
|
+ if (input->pkt.stream_index != input->stream_index)
|
|
+ continue;
|
|
+ input->curr_pkt_size = input->pkt.size;
|
|
+ input->curr_pkt_buf = input->pkt.data;
|
|
+ input->curr_size += input->pkt.size;
|
|
+ input->curr_duration += input->pkt.duration;
|
|
+
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
- int send_result = avcodec_send_packet(cc, &avpkt);
|
|
- if (send_result != 0) {
|
|
- if (send_result != AVERROR(EAGAIN)) {
|
|
- d_print("avcodec_send_packet() returned %d\n", send_result);
|
|
- char errstr[AV_ERROR_MAX_STRING_SIZE];
|
|
- if (!av_strerror(send_result, errstr, AV_ERROR_MAX_STRING_SIZE ))
|
|
- {
|
|
- d_print("av_strerror(): %s\n", errstr);
|
|
- } else {
|
|
- d_print("av_strerror(): Description for error cannot be found\n");
|
|
- }
|
|
- av_packet_unref(&avpkt);
|
|
- return -IP_ERROR_INTERNAL;
|
|
+ int send_result = avcodec_send_packet(cc, &input->pkt);
|
|
+ if (send_result != 0 && send_result != AVERROR(EAGAIN)) {
|
|
+ d_print("avcodec_send_packet() returned %d\n", send_result);
|
|
+ char errstr[AV_ERROR_MAX_STRING_SIZE];
|
|
+ if (!av_strerror(send_result, errstr, AV_ERROR_MAX_STRING_SIZE ))
|
|
+ {
|
|
+ d_print("av_strerror(): %s\n", errstr);
|
|
+ } else {
|
|
+ d_print("av_strerror(): Description for error cannot be found\n");
|
|
}
|
|
- len = 0;
|
|
- } else {
|
|
- len = input->curr_pkt_size;
|
|
+ return -IP_ERROR_INTERNAL;
|
|
}
|
|
-
|
|
- int recv_result = avcodec_receive_frame(cc, frame);
|
|
- got_frame = (recv_result == 0) ? 1 : 0;
|
|
-#else
|
|
- len = avcodec_decode_audio4(cc, frame, &got_frame, &avpkt);
|
|
-#endif
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 56
|
|
- av_packet_unref(&avpkt);
|
|
-#else
|
|
- av_free_packet(&avpkt);
|
|
#endif
|
|
}
|
|
+
|
|
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
+ int recv_result = avcodec_receive_frame(cc, frame);
|
|
+ if (recv_result < 0) {
|
|
+ input->curr_pkt_size = 0;
|
|
+ continue;
|
|
+ }
|
|
+#else
|
|
+ int got_frame;
|
|
+ int len = avcodec_decode_audio4(cc, frame, &got_frame, &input->pkt);
|
|
if (len < 0) {
|
|
/* this is often reached when seeking, not sure why */
|
|
input->curr_pkt_size = 0;
|
|
continue;
|
|
}
|
|
- input->curr_pkt_size -= len;
|
|
- input->curr_pkt_buf += len;
|
|
- if (got_frame) {
|
|
- int res = swr_convert(swr,
|
|
- &output->buffer,
|
|
- frame->nb_samples,
|
|
- (const uint8_t **)frame->extended_data,
|
|
- frame->nb_samples);
|
|
- if (res < 0)
|
|
- res = 0;
|
|
- output->buffer_pos = output->buffer;
|
|
+ if (!got_frame)
|
|
+ continue;
|
|
+#endif
|
|
+
|
|
+ int64_t frame_ts = -1;
|
|
+ if (frame->pts)
|
|
+ frame_ts = frame->pts;
|
|
+ else if (frame->pkt_pts)
|
|
+ frame_ts = frame->pkt_pts;
|
|
+ else if (frame->pkt_dts)
|
|
+ frame_ts = frame->pkt_dts;
|
|
+
|
|
+ const uint8_t **in = (const uint8_t **)frame->extended_data;
|
|
+ int in_count = frame->nb_samples;
|
|
+ if (input->seek_ts > 0 && (frame_ts >= 0 || input->prev_frame_end >= 0)) {
|
|
+ struct ffmpeg_private *priv = ip_data->private;
|
|
+ AVStream *st = priv->input_context->streams[priv->input->stream_index];
|
|
+ if (frame_ts >= 0)
|
|
+ frame_ts = av_rescale_q(frame_ts, st->time_base, AV_TIME_BASE_Q);
|
|
+ else
|
|
+ frame_ts = input->prev_frame_end;
|
|
+ int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, sf_get_rate(ip_data->sf));
|
|
+ int64_t frame_end = frame_ts + frame_dur;
|
|
+ input->prev_frame_end = frame_end;
|
|
+ d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
|
|
+ if (frame_end <= input->seek_ts)
|
|
+ continue;
|
|
+
|
|
+ /* skip part of this frame */
|
|
+ int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, sf_get_rate(ip_data->sf), AV_TIME_BASE);
|
|
+ in_count -= skip_samples;
|
|
+ if (av_sample_fmt_is_planar(frame->format)) {
|
|
+ for (int i = 0; i < cc->channels; i++) {
|
|
+ in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
|
|
+ }
|
|
+ } else {
|
|
+ *in += skip_samples * cc->channels * sf_get_sample_size(ip_data->sf);
|
|
+ }
|
|
+
|
|
+ input->seek_ts = -1;
|
|
+ input->prev_frame_end = -1;
|
|
+ }
|
|
+
|
|
+ int res = swr_convert(swr,
|
|
+ &output->buffer,
|
|
+ frame->nb_samples,
|
|
+ in,
|
|
+ in_count);
|
|
+ if (res < 0)
|
|
+ res = 0;
|
|
+
|
|
+ output->buffer_pos = output->buffer;
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 60
|
|
- output->buffer_used_len = res * cc->ch_layout.nb_channels * sf_get_sample_size(ip_data->sf);
|
|
+ output->buffer_used_len = res * cc->ch_layout.nb_channels * sf_get_sample_size(ip_data->sf);
|
|
#else
|
|
- output->buffer_used_len = res * cc->channels * sf_get_sample_size(ip_data->sf);
|
|
+ output->buffer_used_len = res * cc->channels * sf_get_sample_size(ip_data->sf);
|
|
#endif
|
|
+
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 56
|
|
- av_frame_free(&frame);
|
|
+ av_frame_free(&frame);
|
|
#else
|
|
- avcodec_free_frame(&frame);
|
|
+ avcodec_free_frame(&frame);
|
|
#endif
|
|
- return output->buffer_used_len;
|
|
- }
|
|
+ return output->buffer_used_len;
|
|
}
|
|
/* This should never get here. */
|
|
return -IP_ERROR_INTERNAL;
|
|
@@ -437,13 +468,16 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
|
|
AVStream *st = priv->input_context->streams[priv->input->stream_index];
|
|
int ret;
|
|
|
|
- int64_t pts = av_rescale_q(offset * AV_TIME_BASE, AV_TIME_BASE_Q, st->time_base);
|
|
+ priv->input->seek_ts = offset * AV_TIME_BASE;
|
|
+ priv->input->prev_frame_end = -1;
|
|
+ int64_t ts = av_rescale(offset, st->time_base.den, st->time_base.num);
|
|
|
|
avcodec_flush_buffers(priv->codec_context);
|
|
/* Force reading a new packet in next ffmpeg_fill_buffer(). */
|
|
priv->input->curr_pkt_size = 0;
|
|
|
|
- ret = av_seek_frame(priv->input_context, priv->input->stream_index, pts, 0);
|
|
+ ret = avformat_seek_file(priv->input_context,
|
|
+ priv->input->stream_index, 0, ts, ts, 0);
|
|
|
|
if (ret < 0) {
|
|
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
|
|
|
From d4c60887daca3700be3df99ea5067bbddc1b9a26 Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Fri, 15 Aug 2025 21:42:19 +0300
|
|
Subject: [PATCH 02/13] ip/ffmpeg: skip samples only when needed
|
|
|
|
---
|
|
ip/ffmpeg.c | 32 ++++++++++++++++++--------------
|
|
1 file changed, 18 insertions(+), 14 deletions(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index ecbf00582..5f5a4f37b 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -393,22 +393,26 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
|
|
frame_ts = av_rescale_q(frame_ts, st->time_base, AV_TIME_BASE_Q);
|
|
else
|
|
frame_ts = input->prev_frame_end;
|
|
- int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, sf_get_rate(ip_data->sf));
|
|
- int64_t frame_end = frame_ts + frame_dur;
|
|
- input->prev_frame_end = frame_end;
|
|
- d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
|
|
- if (frame_end <= input->seek_ts)
|
|
- continue;
|
|
|
|
- /* skip part of this frame */
|
|
- int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, sf_get_rate(ip_data->sf), AV_TIME_BASE);
|
|
- in_count -= skip_samples;
|
|
- if (av_sample_fmt_is_planar(frame->format)) {
|
|
- for (int i = 0; i < cc->channels; i++) {
|
|
- in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
|
|
+ if (frame_ts < input->seek_ts) {
|
|
+ int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, sf_get_rate(ip_data->sf));
|
|
+ int64_t frame_end = frame_ts + frame_dur;
|
|
+ input->prev_frame_end = frame_end;
|
|
+ d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
|
|
+ if (frame_end <= input->seek_ts)
|
|
+ continue;
|
|
+
|
|
+ /* skip part of this frame */
|
|
+ int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, sf_get_rate(ip_data->sf), AV_TIME_BASE);
|
|
+ in_count -= skip_samples;
|
|
+ if (av_sample_fmt_is_planar(frame->format)) {
|
|
+ for (int i = 0; i < cc->channels; i++) {
|
|
+ in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
|
|
+ }
|
|
+ } else {
|
|
+ *in += skip_samples * cc->channels * sf_get_sample_size(ip_data->sf);
|
|
}
|
|
- } else {
|
|
- *in += skip_samples * cc->channels * sf_get_sample_size(ip_data->sf);
|
|
+ d_print("skipping %ld samples\n", skip_samples);
|
|
}
|
|
|
|
input->seek_ts = -1;
|
|
|
|
From 07e4c10d1f61575d6acbd457a1dcb4d57d8b6535 Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Sat, 16 Aug 2025 02:43:55 +0300
|
|
Subject: [PATCH 03/13] ip/ffmpeg: remove excessive version checks
|
|
|
|
ffmpeg download page states that v4.0.6 has
|
|
- libavutil 56.14.100
|
|
- libavcodec 58.18.100
|
|
- libavformat 58.12.100
|
|
(https://ffmpeg.org/olddownload.html)
|
|
|
|
After removing all checks for versions lower than these, the plugin
|
|
still compiles with v3.3.9 headers.
|
|
|
|
After all, why be better with compatibility than developers themselves?
|
|
---
|
|
ip/ffmpeg.c | 109 +++++++++++-----------------------------------------
|
|
1 file changed, 23 insertions(+), 86 deletions(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index 5f5a4f37b..f6a11f450 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -25,7 +25,6 @@
|
|
#include "../config/ffmpeg.h"
|
|
#endif
|
|
|
|
-#include <stdio.h>
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libavformat/avformat.h>
|
|
#include <libavformat/avio.h>
|
|
@@ -43,7 +42,6 @@
|
|
struct ffmpeg_input {
|
|
AVPacket pkt;
|
|
int curr_pkt_size;
|
|
- uint8_t *curr_pkt_buf;
|
|
int64_t seek_ts;
|
|
int64_t prev_frame_end;
|
|
int stream_index;
|
|
@@ -80,17 +78,12 @@ static struct ffmpeg_input *ffmpeg_input_create(void)
|
|
input->curr_pkt_size = 0;
|
|
input->seek_ts = -1;
|
|
input->prev_frame_end = -1;
|
|
- input->curr_pkt_buf = input->pkt.data;
|
|
return input;
|
|
}
|
|
|
|
static void ffmpeg_input_free(struct ffmpeg_input *input)
|
|
{
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 56
|
|
av_packet_unref(&input->pkt);
|
|
-#else
|
|
- av_free_packet(&input->pkt);
|
|
-#endif
|
|
free(input);
|
|
}
|
|
|
|
@@ -132,7 +125,7 @@ static void ffmpeg_init(void)
|
|
|
|
av_log_set_level(AV_LOG_QUIET);
|
|
|
|
-#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 18, 100)
|
|
+#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
|
/* We could register decoders explicitly to save memory, but we have to
|
|
* be careful about compatibility. */
|
|
av_register_all();
|
|
@@ -149,9 +142,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
AVCodec const *codec;
|
|
AVCodecContext *cc = NULL;
|
|
AVFormatContext *ic = NULL;
|
|
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
AVCodecParameters *cp = NULL;
|
|
-#endif
|
|
SwrContext *swr = NULL;
|
|
|
|
ffmpeg_init();
|
|
@@ -171,20 +162,11 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
}
|
|
|
|
for (i = 0; i < ic->nb_streams; i++) {
|
|
-
|
|
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
cp = ic->streams[i]->codecpar;
|
|
if (cp->codec_type == AVMEDIA_TYPE_AUDIO) {
|
|
stream_index = i;
|
|
break;
|
|
}
|
|
-#else
|
|
- cc = ic->streams[i]->codec;
|
|
- if (cc->codec_type == AVMEDIA_TYPE_AUDIO) {
|
|
- stream_index = i;
|
|
- break;
|
|
- }
|
|
-#endif
|
|
}
|
|
|
|
if (stream_index == -1) {
|
|
@@ -193,13 +175,9 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
break;
|
|
}
|
|
|
|
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
codec = avcodec_find_decoder(cp->codec_id);
|
|
cc = avcodec_alloc_context3(codec);
|
|
avcodec_parameters_to_context(cc, cp);
|
|
-#else
|
|
- codec = avcodec_find_decoder(cc->codec_id);
|
|
-#endif
|
|
if (!codec) {
|
|
d_print("codec not found: %d, %s\n", cc->codec_id, avcodec_get_name(cc->codec_id));
|
|
err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
|
@@ -217,9 +195,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
|
|
if (err < 0) {
|
|
/* Clean up. cc is never opened at this point. (See above assumption.) */
|
|
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
avcodec_free_context(&cc);
|
|
-#endif
|
|
avformat_close_input(&ic);
|
|
return err;
|
|
}
|
|
@@ -231,9 +207,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
priv->input = ffmpeg_input_create();
|
|
if (priv->input == NULL) {
|
|
avcodec_close(cc);
|
|
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
avcodec_free_context(&cc);
|
|
-#endif
|
|
avformat_close_input(&ic);
|
|
free(priv);
|
|
return -IP_ERROR_INTERNAL;
|
|
@@ -244,7 +218,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
/* Prepare for resampling. */
|
|
out_sample_rate = min_u(cc->sample_rate, 384000);
|
|
swr = swr_alloc();
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 60
|
|
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
|
if (cc->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC)
|
|
av_channel_layout_default(&cc->ch_layout, cc->ch_layout.nb_channels);
|
|
av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0);
|
|
@@ -259,7 +233,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
priv->swr = swr;
|
|
|
|
ip_data->private = priv;
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 60
|
|
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
|
ip_data->sf = sf_rate(out_sample_rate) | sf_channels(cc->ch_layout.nb_channels);
|
|
#else
|
|
ip_data->sf = sf_rate(out_sample_rate) | sf_channels(cc->channels);
|
|
@@ -281,10 +255,12 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
}
|
|
swr_init(swr);
|
|
ip_data->sf |= sf_host_endian();
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 60
|
|
- channel_map_init_waveex(cc->ch_layout.nb_channels, cc->ch_layout.u.mask, ip_data->channel_map);
|
|
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
|
+ channel_map_init_waveex(cc->ch_layout.nb_channels,
|
|
+ cc->ch_layout.u.mask, ip_data->channel_map);
|
|
#else
|
|
- channel_map_init_waveex(cc->channels, cc->channel_layout, ip_data->channel_map);
|
|
+ channel_map_init_waveex(cc->channels,
|
|
+ cc->channel_layout, ip_data->channel_map);
|
|
#endif
|
|
return 0;
|
|
}
|
|
@@ -294,9 +270,7 @@ static int ffmpeg_close(struct input_plugin_data *ip_data)
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
|
|
avcodec_close(priv->codec_context);
|
|
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
avcodec_free_context(&priv->codec_context);
|
|
-#endif
|
|
avformat_close_input(&priv->input_context);
|
|
swr_free(&priv->swr);
|
|
ffmpeg_input_free(priv->input);
|
|
@@ -310,39 +284,27 @@ static int ffmpeg_close(struct input_plugin_data *ip_data)
|
|
* This returns the number of bytes added to the buffer.
|
|
* It returns < 0 on error. 0 on EOF.
|
|
*/
|
|
-static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext *ic, AVCodecContext *cc,
|
|
- struct ffmpeg_input *input, struct ffmpeg_output *output, SwrContext *swr)
|
|
+static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data,
|
|
+ AVFormatContext *ic, AVCodecContext *cc,
|
|
+ struct ffmpeg_input *input, struct ffmpeg_output *output,
|
|
+ SwrContext *swr)
|
|
{
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 56
|
|
AVFrame *frame = av_frame_alloc();
|
|
-#else
|
|
- AVFrame *frame = avcodec_alloc_frame();
|
|
-#endif
|
|
while (1) {
|
|
if (input->curr_pkt_size <= 0) {
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 56
|
|
av_packet_unref(&input->pkt);
|
|
-#else
|
|
- av_free_packet(&input->pkt);
|
|
-#endif
|
|
if (av_read_frame(ic, &input->pkt) < 0) {
|
|
/* Force EOF once we can read no longer. */
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 56
|
|
av_frame_free(&frame);
|
|
-#else
|
|
- avcodec_free_frame(&frame);
|
|
-#endif
|
|
return 0;
|
|
}
|
|
|
|
if (input->pkt.stream_index != input->stream_index)
|
|
continue;
|
|
input->curr_pkt_size = input->pkt.size;
|
|
- input->curr_pkt_buf = input->pkt.data;
|
|
input->curr_size += input->pkt.size;
|
|
input->curr_duration += input->pkt.duration;
|
|
|
|
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
int send_result = avcodec_send_packet(cc, &input->pkt);
|
|
if (send_result != 0 && send_result != AVERROR(EAGAIN)) {
|
|
d_print("avcodec_send_packet() returned %d\n", send_result);
|
|
@@ -355,32 +317,17 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
|
|
}
|
|
return -IP_ERROR_INTERNAL;
|
|
}
|
|
-#endif
|
|
}
|
|
|
|
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
|
|
int recv_result = avcodec_receive_frame(cc, frame);
|
|
if (recv_result < 0) {
|
|
input->curr_pkt_size = 0;
|
|
continue;
|
|
}
|
|
-#else
|
|
- int got_frame;
|
|
- int len = avcodec_decode_audio4(cc, frame, &got_frame, &input->pkt);
|
|
- if (len < 0) {
|
|
- /* this is often reached when seeking, not sure why */
|
|
- input->curr_pkt_size = 0;
|
|
- continue;
|
|
- }
|
|
- if (!got_frame)
|
|
- continue;
|
|
-#endif
|
|
|
|
int64_t frame_ts = -1;
|
|
if (frame->pts)
|
|
frame_ts = frame->pts;
|
|
- else if (frame->pkt_pts)
|
|
- frame_ts = frame->pkt_pts;
|
|
else if (frame->pkt_dts)
|
|
frame_ts = frame->pkt_dts;
|
|
|
|
@@ -395,7 +342,7 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
|
|
frame_ts = input->prev_frame_end;
|
|
|
|
if (frame_ts < input->seek_ts) {
|
|
- int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, sf_get_rate(ip_data->sf));
|
|
+ int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, frame->sample_rate);
|
|
int64_t frame_end = frame_ts + frame_dur;
|
|
input->prev_frame_end = frame_end;
|
|
d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
|
|
@@ -403,14 +350,14 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
|
|
continue;
|
|
|
|
/* skip part of this frame */
|
|
- int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, sf_get_rate(ip_data->sf), AV_TIME_BASE);
|
|
+ int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, frame->sample_rate, AV_TIME_BASE);
|
|
in_count -= skip_samples;
|
|
if (av_sample_fmt_is_planar(frame->format)) {
|
|
- for (int i = 0; i < cc->channels; i++) {
|
|
+ for (int i = 0; i < sf_get_channels(ip_data->sf); i++) {
|
|
in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
|
|
}
|
|
} else {
|
|
- *in += skip_samples * cc->channels * sf_get_sample_size(ip_data->sf);
|
|
+ *in += skip_samples * sf_get_frame_size(ip_data->sf);
|
|
}
|
|
d_print("skipping %ld samples\n", skip_samples);
|
|
}
|
|
@@ -428,17 +375,9 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
|
|
res = 0;
|
|
|
|
output->buffer_pos = output->buffer;
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 60
|
|
- output->buffer_used_len = res * cc->ch_layout.nb_channels * sf_get_sample_size(ip_data->sf);
|
|
-#else
|
|
- output->buffer_used_len = res * cc->channels * sf_get_sample_size(ip_data->sf);
|
|
-#endif
|
|
+ output->buffer_used_len = res * sf_get_frame_size(ip_data->sf);
|
|
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 56
|
|
av_frame_free(&frame);
|
|
-#else
|
|
- avcodec_free_frame(&frame);
|
|
-#endif
|
|
return output->buffer_used_len;
|
|
}
|
|
/* This should never get here. */
|
|
@@ -453,11 +392,11 @@ static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int coun
|
|
int out_size;
|
|
|
|
if (output->buffer_used_len == 0) {
|
|
- rc = ffmpeg_fill_buffer(ip_data, priv->input_context, priv->codec_context,
|
|
+ rc = ffmpeg_fill_buffer(ip_data,
|
|
+ priv->input_context, priv->codec_context,
|
|
priv->input, priv->output, priv->swr);
|
|
- if (rc <= 0) {
|
|
+ if (rc <= 0)
|
|
return rc;
|
|
- }
|
|
}
|
|
out_size = min_i(output->buffer_used_len, count);
|
|
memcpy(buffer, output->buffer_pos, out_size);
|
|
@@ -477,6 +416,7 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
|
|
int64_t ts = av_rescale(offset, st->time_base.den, st->time_base.num);
|
|
|
|
avcodec_flush_buffers(priv->codec_context);
|
|
+ /* TODO: also flush swresample buffers */
|
|
/* Force reading a new packet in next ffmpeg_fill_buffer(). */
|
|
priv->input->curr_pkt_size = 0;
|
|
|
|
@@ -501,7 +441,8 @@ static void ffmpeg_read_metadata(struct growing_keyvals *c, AVDictionary *metada
|
|
}
|
|
}
|
|
|
|
-static int ffmpeg_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
|
+static int ffmpeg_read_comments(struct input_plugin_data *ip_data,
|
|
+ struct keyval **comments)
|
|
{
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
AVFormatContext *ic = priv->input_context;
|
|
@@ -538,11 +479,7 @@ static long ffmpeg_current_bitrate(struct input_plugin_data *ip_data)
|
|
AVStream *st = priv->input_context->streams[priv->input->stream_index];
|
|
long bitrate = -1;
|
|
/* ape codec returns silly numbers */
|
|
-#if LIBAVCODEC_VERSION_MAJOR >= 55
|
|
if (priv->codec->id == AV_CODEC_ID_APE)
|
|
-#else
|
|
- if (priv->codec->id == CODEC_ID_APE)
|
|
-#endif
|
|
return -1;
|
|
if (priv->input->curr_duration > 0) {
|
|
double seconds = priv->input->curr_duration * av_q2d(st->time_base);
|
|
|
|
From 791d190b532eef4d833d0325e8f56b069168b096 Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Sun, 17 Aug 2025 04:05:36 +0300
|
|
Subject: [PATCH 04/13] ip/ffmpeg: major refactor
|
|
|
|
---
|
|
ip/ffmpeg.c | 643 +++++++++++++++++++++++++++-------------------------
|
|
1 file changed, 330 insertions(+), 313 deletions(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index f6a11f450..42f630ee7 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -35,84 +35,32 @@
|
|
#include <libavutil/mathematics.h>
|
|
#endif
|
|
|
|
-#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE
|
|
-#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000
|
|
-#endif
|
|
+struct ffmpeg_private {
|
|
+ AVCodecContext *codec_ctx;
|
|
+ AVFormatContext *format_ctx;
|
|
+ AVCodec const *codec;
|
|
+ SwrContext *swr;
|
|
+ int stream_index;
|
|
|
|
-struct ffmpeg_input {
|
|
- AVPacket pkt;
|
|
- int curr_pkt_size;
|
|
+ AVPacket *pkt;
|
|
+ AVFrame *frame;
|
|
int64_t seek_ts;
|
|
int64_t prev_frame_end;
|
|
- int stream_index;
|
|
|
|
+ /* A buffer to hold swr_convert()-ed samples */
|
|
+ AVFrame *swr_frame;
|
|
+ int swr_frame_start;
|
|
+
|
|
+ /* Bitrate estimation */
|
|
unsigned long curr_size;
|
|
unsigned long curr_duration;
|
|
};
|
|
|
|
-struct ffmpeg_output {
|
|
- uint8_t *buffer;
|
|
- uint8_t *buffer_malloc;
|
|
- uint8_t *buffer_pos; /* current buffer position */
|
|
- int buffer_used_len;
|
|
-};
|
|
-
|
|
-struct ffmpeg_private {
|
|
- AVCodecContext *codec_context;
|
|
- AVFormatContext *input_context;
|
|
- AVCodec const *codec;
|
|
- SwrContext *swr;
|
|
-
|
|
- struct ffmpeg_input *input;
|
|
- struct ffmpeg_output *output;
|
|
-};
|
|
-
|
|
-static struct ffmpeg_input *ffmpeg_input_create(void)
|
|
-{
|
|
- struct ffmpeg_input *input = xnew(struct ffmpeg_input, 1);
|
|
-
|
|
- if (av_new_packet(&input->pkt, 0) != 0) {
|
|
- free(input);
|
|
- return NULL;
|
|
- }
|
|
- input->curr_pkt_size = 0;
|
|
- input->seek_ts = -1;
|
|
- input->prev_frame_end = -1;
|
|
- return input;
|
|
-}
|
|
-
|
|
-static void ffmpeg_input_free(struct ffmpeg_input *input)
|
|
-{
|
|
- av_packet_unref(&input->pkt);
|
|
- free(input);
|
|
-}
|
|
-
|
|
-static struct ffmpeg_output *ffmpeg_output_create(void)
|
|
-{
|
|
- struct ffmpeg_output *output = xnew(struct ffmpeg_output, 1);
|
|
-
|
|
- output->buffer_malloc = xnew(uint8_t, AVCODEC_MAX_AUDIO_FRAME_SIZE + 15);
|
|
- output->buffer = output->buffer_malloc;
|
|
- /* align to 16 bytes so avcodec can SSE/Altivec/etc */
|
|
- while ((intptr_t) output->buffer % 16)
|
|
- output->buffer += 1;
|
|
- output->buffer_pos = output->buffer;
|
|
- output->buffer_used_len = 0;
|
|
- return output;
|
|
-}
|
|
-
|
|
-static void ffmpeg_output_free(struct ffmpeg_output *output)
|
|
-{
|
|
- free(output->buffer_malloc);
|
|
- output->buffer_malloc = NULL;
|
|
- output->buffer = NULL;
|
|
- free(output);
|
|
-}
|
|
-
|
|
-static inline void ffmpeg_buffer_flush(struct ffmpeg_output *output)
|
|
+static const char *ffmpeg_errmsg(int err)
|
|
{
|
|
- output->buffer_pos = output->buffer;
|
|
- output->buffer_used_len = 0;
|
|
+ static char errstr[AV_ERROR_MAX_STRING_SIZE];
|
|
+ av_strerror(err, errstr, AV_ERROR_MAX_STRING_SIZE);
|
|
+ return errstr;
|
|
}
|
|
|
|
static void ffmpeg_init(void)
|
|
@@ -132,303 +80,372 @@ static void ffmpeg_init(void)
|
|
#endif
|
|
}
|
|
|
|
-static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
+static int ffmpeg_open_input(struct input_plugin_data *ip_data,
|
|
+ struct ffmpeg_private *priv)
|
|
{
|
|
- struct ffmpeg_private *priv;
|
|
- int err = 0;
|
|
- int i;
|
|
- int stream_index = -1;
|
|
- int out_sample_rate;
|
|
- AVCodec const *codec;
|
|
- AVCodecContext *cc = NULL;
|
|
AVFormatContext *ic = NULL;
|
|
+ AVCodecContext *cc = NULL;
|
|
AVCodecParameters *cp = NULL;
|
|
- SwrContext *swr = NULL;
|
|
-
|
|
- ffmpeg_init();
|
|
+ AVCodec const *codec = NULL;
|
|
+ int stream_index = -1;
|
|
|
|
- err = avformat_open_input(&ic, ip_data->filename, NULL, NULL);
|
|
- if (err < 0) {
|
|
- d_print("av_open failed: %d\n", err);
|
|
- return -IP_ERROR_FILE_FORMAT;
|
|
+ int err;
|
|
+ int res = avformat_open_input(&ic, ip_data->filename, NULL, NULL);
|
|
+ if (res < 0) {
|
|
+ err = -IP_ERROR_FILE_FORMAT;
|
|
+ goto err;
|
|
}
|
|
|
|
- do {
|
|
- err = avformat_find_stream_info(ic, NULL);
|
|
- if (err < 0) {
|
|
- d_print("unable to find stream info: %d\n", err);
|
|
- err = -IP_ERROR_FILE_FORMAT;
|
|
- break;
|
|
- }
|
|
-
|
|
- for (i = 0; i < ic->nb_streams; i++) {
|
|
- cp = ic->streams[i]->codecpar;
|
|
- if (cp->codec_type == AVMEDIA_TYPE_AUDIO) {
|
|
- stream_index = i;
|
|
- break;
|
|
- }
|
|
- }
|
|
-
|
|
- if (stream_index == -1) {
|
|
- d_print("could not find audio stream\n");
|
|
- err = -IP_ERROR_FILE_FORMAT;
|
|
- break;
|
|
- }
|
|
-
|
|
- codec = avcodec_find_decoder(cp->codec_id);
|
|
- cc = avcodec_alloc_context3(codec);
|
|
- avcodec_parameters_to_context(cc, cp);
|
|
- if (!codec) {
|
|
- d_print("codec not found: %d, %s\n", cc->codec_id, avcodec_get_name(cc->codec_id));
|
|
- err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
|
- break;
|
|
- }
|
|
+ res = avformat_find_stream_info(ic, NULL);
|
|
+ if (res < 0) {
|
|
+ d_print("unable to find stream info\n");
|
|
+ err = -IP_ERROR_FILE_FORMAT;
|
|
+ goto err;
|
|
+ }
|
|
|
|
- if (avcodec_open2(cc, codec, NULL) < 0) {
|
|
- d_print("could not open codec: %d, %s\n", cc->codec_id, avcodec_get_name(cc->codec_id));
|
|
- err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
|
+ for (int i = 0; i < ic->nb_streams; i++) {
|
|
+ cp = ic->streams[i]->codecpar;
|
|
+ if (cp->codec_type == AVMEDIA_TYPE_AUDIO) {
|
|
+ stream_index = i;
|
|
break;
|
|
}
|
|
+ }
|
|
|
|
- /* We assume below that no more errors follow. */
|
|
- } while (0);
|
|
+ if (stream_index == -1) {
|
|
+ d_print("could not find audio stream\n");
|
|
+ err = -IP_ERROR_FILE_FORMAT;
|
|
+ goto err_silent;
|
|
+ }
|
|
|
|
- if (err < 0) {
|
|
- /* Clean up. cc is never opened at this point. (See above assumption.) */
|
|
- avcodec_free_context(&cc);
|
|
- avformat_close_input(&ic);
|
|
- return err;
|
|
+ codec = avcodec_find_decoder(cp->codec_id);
|
|
+ if (!codec) {
|
|
+ d_print("codec (id: %d, name: %s) not found\n",
|
|
+ cc->codec_id, avcodec_get_name(cc->codec_id));
|
|
+ err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
|
+ goto err_silent;
|
|
+ }
|
|
+ cc = avcodec_alloc_context3(codec);
|
|
+ avcodec_parameters_to_context(cc, cp);
|
|
+
|
|
+ res = avcodec_open2(cc, codec, NULL);
|
|
+ if (res < 0) {
|
|
+ d_print("could not open codec (id: %d, name: %s)\n",
|
|
+ cc->codec_id, avcodec_get_name(cc->codec_id));
|
|
+ err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
|
+ goto err;
|
|
}
|
|
|
|
- priv = xnew(struct ffmpeg_private, 1);
|
|
- priv->codec_context = cc;
|
|
- priv->input_context = ic;
|
|
+ priv->format_ctx = ic;
|
|
+ priv->codec_ctx = cc;
|
|
priv->codec = codec;
|
|
- priv->input = ffmpeg_input_create();
|
|
- if (priv->input == NULL) {
|
|
- avcodec_close(cc);
|
|
- avcodec_free_context(&cc);
|
|
- avformat_close_input(&ic);
|
|
- free(priv);
|
|
- return -IP_ERROR_INTERNAL;
|
|
+ priv->stream_index = stream_index;
|
|
+ return 0;
|
|
+err:
|
|
+ d_print("%s\n", ffmpeg_errmsg(res));
|
|
+err_silent:
|
|
+ avcodec_free_context(&cc);
|
|
+ avformat_close_input(&ic);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void ffmpeg_set_sf_and_swr_opts(SwrContext *swr, AVCodecContext *cc,
|
|
+ sample_format_t *sf_out, enum AVSampleFormat *out_sample_fmt)
|
|
+{
|
|
+ int out_sample_rate = min_u(cc->sample_rate, 384000);
|
|
+ sample_format_t sf = sf_rate(out_sample_rate) | sf_host_endian();
|
|
+ av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0);
|
|
+ av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0);
|
|
+
|
|
+ *out_sample_fmt = cc->sample_fmt;
|
|
+ switch (*out_sample_fmt) {
|
|
+ case AV_SAMPLE_FMT_U8:
|
|
+ sf |= sf_bits(8) | sf_signed(0);
|
|
+ break;
|
|
+ case AV_SAMPLE_FMT_S32:
|
|
+ sf |= sf_bits(32) | sf_signed(1);
|
|
+ break;
|
|
+ default:
|
|
+ sf |= sf_bits(16) | sf_signed(1);
|
|
+ *out_sample_fmt = AV_SAMPLE_FMT_S16;
|
|
}
|
|
- priv->input->stream_index = stream_index;
|
|
- priv->output = ffmpeg_output_create();
|
|
+ av_opt_set_sample_fmt(swr, "in_sample_fmt", cc->sample_fmt, 0);
|
|
+ av_opt_set_sample_fmt(swr, "out_sample_fmt", *out_sample_fmt, 0);
|
|
|
|
- /* Prepare for resampling. */
|
|
- out_sample_rate = min_u(cc->sample_rate, 384000);
|
|
- swr = swr_alloc();
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
|
+ sf |= sf_channels(cc->ch_layout.nb_channels);
|
|
+
|
|
if (cc->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC)
|
|
av_channel_layout_default(&cc->ch_layout, cc->ch_layout.nb_channels);
|
|
- av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0);
|
|
- av_opt_set_chlayout(swr, "out_chlayout", &cc->ch_layout, 0);
|
|
+ av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0);
|
|
+ av_opt_set_chlayout(swr, "out_chlayout", &cc->ch_layout, 0);
|
|
#else
|
|
- av_opt_set_int(swr, "in_channel_layout", av_get_default_channel_layout(cc->channels), 0);
|
|
- av_opt_set_int(swr, "out_channel_layout", av_get_default_channel_layout(cc->channels), 0);
|
|
+ sf |= sf_channels(cc->channels);
|
|
+
|
|
+ av_opt_set_int(swr, "in_channel_layout",
|
|
+ av_get_default_channel_layout(cc->channels), 0);
|
|
+ av_opt_set_int(swr, "out_channel_layout",
|
|
+ av_get_default_channel_layout(cc->channels), 0);
|
|
#endif
|
|
- av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0);
|
|
- av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0);
|
|
- av_opt_set_sample_fmt(swr, "in_sample_fmt", cc->sample_fmt, 0);
|
|
- priv->swr = swr;
|
|
|
|
- ip_data->private = priv;
|
|
+ *sf_out = sf;
|
|
+}
|
|
+
|
|
+static int ffmpeg_init_swr_frame(struct ffmpeg_private *priv,
|
|
+ sample_format_t sf, enum AVSampleFormat out_sample_fmt)
|
|
+{
|
|
+ AVCodecContext *cc = priv->codec_ctx;
|
|
+ AVFrame *frame = av_frame_alloc();
|
|
+
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
|
- ip_data->sf = sf_rate(out_sample_rate) | sf_channels(cc->ch_layout.nb_channels);
|
|
+ av_channel_layout_copy(&frame->ch_layout, &cc->ch_layout);
|
|
#else
|
|
- ip_data->sf = sf_rate(out_sample_rate) | sf_channels(cc->channels);
|
|
+ frame->channel_layout = av_get_default_channel_layout(cc->channels);
|
|
#endif
|
|
- switch (cc->sample_fmt) {
|
|
- case AV_SAMPLE_FMT_U8:
|
|
- ip_data->sf |= sf_bits(8) | sf_signed(0);
|
|
- av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_U8, 0);
|
|
- break;
|
|
- case AV_SAMPLE_FMT_S32:
|
|
- ip_data->sf |= sf_bits(32) | sf_signed(1);
|
|
- av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S32, 0);
|
|
- break;
|
|
- /* AV_SAMPLE_FMT_S16 */
|
|
- default:
|
|
- ip_data->sf |= sf_bits(16) | sf_signed(1);
|
|
- av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
|
- break;
|
|
+
|
|
+ frame->sample_rate = sf_get_rate(sf);
|
|
+ frame->format = out_sample_fmt;
|
|
+
|
|
+ /* NOTE: 10 sec is probably too much, but the amount of space
|
|
+ * needed for swr_convert() is unpredictable */
|
|
+ frame->nb_samples = 10 * sf_get_rate(sf);
|
|
+ int res = av_frame_get_buffer(frame, 0);
|
|
+ if (res < 0) {
|
|
+ d_print("av_frame_get_buffer(): %s\n", ffmpeg_errmsg(res));
|
|
+ return -IP_ERROR_INTERNAL;
|
|
}
|
|
- swr_init(swr);
|
|
- ip_data->sf |= sf_host_endian();
|
|
+ frame->nb_samples = 0;
|
|
+
|
|
+ priv->swr_frame = frame;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ffmpeg_free(struct ffmpeg_private *priv)
|
|
+{
|
|
+ avcodec_close(priv->codec_ctx);
|
|
+ avcodec_free_context(&priv->codec_ctx);
|
|
+ avformat_close_input(&priv->format_ctx);
|
|
+
|
|
+ swr_free(&priv->swr);
|
|
+
|
|
+ av_frame_free(&priv->frame);
|
|
+ av_packet_free(&priv->pkt);
|
|
+ av_frame_free(&priv->swr_frame);
|
|
+}
|
|
+
|
|
+static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
+{
|
|
+ struct ffmpeg_private priv;
|
|
+ enum AVSampleFormat out_sample_fmt;
|
|
+ memset(&priv, 0, sizeof(struct ffmpeg_private));
|
|
+
|
|
+ ffmpeg_init();
|
|
+
|
|
+ int err = ffmpeg_open_input(ip_data, &priv);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ priv.pkt = av_packet_alloc();
|
|
+ priv.frame = av_frame_alloc();
|
|
+ priv.seek_ts = -1;
|
|
+ priv.prev_frame_end = -1;
|
|
+
|
|
+ priv.swr = swr_alloc();
|
|
+ ffmpeg_set_sf_and_swr_opts(priv.swr, priv.codec_ctx,
|
|
+ &ip_data->sf, &out_sample_fmt);
|
|
+ swr_init(priv.swr);
|
|
+
|
|
+ err = ffmpeg_init_swr_frame(&priv, ip_data->sf, out_sample_fmt);
|
|
+ if (err < 0) {
|
|
+ ffmpeg_free(&priv);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
|
- channel_map_init_waveex(cc->ch_layout.nb_channels,
|
|
- cc->ch_layout.u.mask, ip_data->channel_map);
|
|
+ channel_map_init_waveex(priv.codec_ctx->ch_layout.nb_channels,
|
|
+ priv.codec_ctx->ch_layout.u.mask, ip_data->channel_map);
|
|
#else
|
|
- channel_map_init_waveex(cc->channels,
|
|
- cc->channel_layout, ip_data->channel_map);
|
|
+ channel_map_init_waveex(priv.codec_ctx->channels,
|
|
+ priv.codec_ctx->channel_layout, ip_data->channel_map);
|
|
#endif
|
|
+
|
|
+ ip_data->private = xnew(struct ffmpeg_private, 1);
|
|
+ memcpy(ip_data->private, &priv, sizeof(struct ffmpeg_private));
|
|
return 0;
|
|
}
|
|
|
|
static int ffmpeg_close(struct input_plugin_data *ip_data)
|
|
{
|
|
- struct ffmpeg_private *priv = ip_data->private;
|
|
-
|
|
- avcodec_close(priv->codec_context);
|
|
- avcodec_free_context(&priv->codec_context);
|
|
- avformat_close_input(&priv->input_context);
|
|
- swr_free(&priv->swr);
|
|
- ffmpeg_input_free(priv->input);
|
|
- ffmpeg_output_free(priv->output);
|
|
- free(priv);
|
|
+ ffmpeg_free(ip_data->private);
|
|
+ free(ip_data->private);
|
|
ip_data->private = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
- * This returns the number of bytes added to the buffer.
|
|
- * It returns < 0 on error. 0 on EOF.
|
|
+ * return:
|
|
+ * 0 - retry
|
|
+ * >0 - ok
|
|
*/
|
|
-static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data,
|
|
- AVFormatContext *ic, AVCodecContext *cc,
|
|
- struct ffmpeg_input *input, struct ffmpeg_output *output,
|
|
- SwrContext *swr)
|
|
+static int ffmpeg_seek_into_frame(struct ffmpeg_private *priv, int64_t frame_ts)
|
|
{
|
|
- AVFrame *frame = av_frame_alloc();
|
|
- while (1) {
|
|
- if (input->curr_pkt_size <= 0) {
|
|
- av_packet_unref(&input->pkt);
|
|
- if (av_read_frame(ic, &input->pkt) < 0) {
|
|
- /* Force EOF once we can read no longer. */
|
|
- av_frame_free(&frame);
|
|
- return 0;
|
|
- }
|
|
-
|
|
- if (input->pkt.stream_index != input->stream_index)
|
|
- continue;
|
|
- input->curr_pkt_size = input->pkt.size;
|
|
- input->curr_size += input->pkt.size;
|
|
- input->curr_duration += input->pkt.duration;
|
|
-
|
|
- int send_result = avcodec_send_packet(cc, &input->pkt);
|
|
- if (send_result != 0 && send_result != AVERROR(EAGAIN)) {
|
|
- d_print("avcodec_send_packet() returned %d\n", send_result);
|
|
- char errstr[AV_ERROR_MAX_STRING_SIZE];
|
|
- if (!av_strerror(send_result, errstr, AV_ERROR_MAX_STRING_SIZE ))
|
|
- {
|
|
- d_print("av_strerror(): %s\n", errstr);
|
|
- } else {
|
|
- d_print("av_strerror(): Description for error cannot be found\n");
|
|
- }
|
|
- return -IP_ERROR_INTERNAL;
|
|
- }
|
|
- }
|
|
+ if (frame_ts >= 0) {
|
|
+ AVStream *s = priv->format_ctx->streams[priv->stream_index];
|
|
+ frame_ts = av_rescale_q(frame_ts, s->time_base, AV_TIME_BASE_Q);
|
|
+ } else {
|
|
+ frame_ts = priv->prev_frame_end;
|
|
+ }
|
|
|
|
- int recv_result = avcodec_receive_frame(cc, frame);
|
|
- if (recv_result < 0) {
|
|
- input->curr_pkt_size = 0;
|
|
- continue;
|
|
- }
|
|
+ if (frame_ts >= priv->seek_ts)
|
|
+ return 1;
|
|
|
|
- int64_t frame_ts = -1;
|
|
- if (frame->pts)
|
|
- frame_ts = frame->pts;
|
|
- else if (frame->pkt_dts)
|
|
- frame_ts = frame->pkt_dts;
|
|
-
|
|
- const uint8_t **in = (const uint8_t **)frame->extended_data;
|
|
- int in_count = frame->nb_samples;
|
|
- if (input->seek_ts > 0 && (frame_ts >= 0 || input->prev_frame_end >= 0)) {
|
|
- struct ffmpeg_private *priv = ip_data->private;
|
|
- AVStream *st = priv->input_context->streams[priv->input->stream_index];
|
|
- if (frame_ts >= 0)
|
|
- frame_ts = av_rescale_q(frame_ts, st->time_base, AV_TIME_BASE_Q);
|
|
- else
|
|
- frame_ts = input->prev_frame_end;
|
|
-
|
|
- if (frame_ts < input->seek_ts) {
|
|
- int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, frame->sample_rate);
|
|
- int64_t frame_end = frame_ts + frame_dur;
|
|
- input->prev_frame_end = frame_end;
|
|
- d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
|
|
- if (frame_end <= input->seek_ts)
|
|
- continue;
|
|
-
|
|
- /* skip part of this frame */
|
|
- int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, frame->sample_rate, AV_TIME_BASE);
|
|
- in_count -= skip_samples;
|
|
- if (av_sample_fmt_is_planar(frame->format)) {
|
|
- for (int i = 0; i < sf_get_channels(ip_data->sf); i++) {
|
|
- in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
|
|
- }
|
|
- } else {
|
|
- *in += skip_samples * sf_get_frame_size(ip_data->sf);
|
|
- }
|
|
- d_print("skipping %ld samples\n", skip_samples);
|
|
- }
|
|
-
|
|
- input->seek_ts = -1;
|
|
- input->prev_frame_end = -1;
|
|
- }
|
|
+ int64_t frame_dur = av_rescale(priv->frame->nb_samples,
|
|
+ AV_TIME_BASE, priv->frame->sample_rate);
|
|
+ int64_t frame_end = frame_ts + frame_dur;
|
|
+ priv->prev_frame_end = frame_end;
|
|
+
|
|
+ d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n",
|
|
+ priv->seek_ts, frame_ts, frame_end);
|
|
+
|
|
+ if (frame_end <= priv->seek_ts)
|
|
+ return 0;
|
|
+
|
|
+ int64_t skip_samples = av_rescale(priv->seek_ts - frame_ts,
|
|
+ priv->frame->sample_rate, AV_TIME_BASE);
|
|
+ priv->frame->nb_samples -= skip_samples;
|
|
+
|
|
+ int bps = av_get_bytes_per_sample(priv->frame->format);
|
|
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
|
+ int channels = priv->codec_ctx->ch_layout.nb_channels;
|
|
+#else
|
|
+ int channels = priv->codec_ctx->channels;
|
|
+#endif
|
|
+
|
|
+ /* Just modify frame's data pointer because it's throw-away */
|
|
+ if (av_sample_fmt_is_planar(priv->frame->format)) {
|
|
+ for (int i = 0; i < channels; i++)
|
|
+ priv->frame->extended_data[i] += skip_samples * bps;
|
|
+ } else {
|
|
+ priv->frame->extended_data[0] += skip_samples * channels * bps;
|
|
+ }
|
|
+ d_print("skipping %ld samples\n", skip_samples);
|
|
+ return 1;
|
|
+}
|
|
|
|
- int res = swr_convert(swr,
|
|
- &output->buffer,
|
|
- frame->nb_samples,
|
|
- in,
|
|
- in_count);
|
|
+/*
|
|
+ * return:
|
|
+ * <0 - error
|
|
+ * 0 - retry
|
|
+ * >0 - ok
|
|
+ */
|
|
+static int ffmpeg_get_frame(struct ffmpeg_private *priv)
|
|
+{
|
|
+ int res = avcodec_receive_frame(priv->codec_ctx, priv->frame);
|
|
+ if (res == AVERROR(EAGAIN)) {
|
|
+ av_packet_unref(priv->pkt);
|
|
+ res = av_read_frame(priv->format_ctx, priv->pkt);
|
|
if (res < 0)
|
|
- res = 0;
|
|
+ return res;
|
|
+
|
|
+ if (priv->pkt->stream_index != priv->stream_index)
|
|
+ return 0;
|
|
|
|
- output->buffer_pos = output->buffer;
|
|
- output->buffer_used_len = res * sf_get_frame_size(ip_data->sf);
|
|
+ priv->curr_size += priv->pkt->size;
|
|
+ priv->curr_duration += priv->pkt->duration;
|
|
|
|
- av_frame_free(&frame);
|
|
- return output->buffer_used_len;
|
|
+ res = avcodec_send_packet(priv->codec_ctx, priv->pkt);
|
|
+ if (res == AVERROR(EAGAIN))
|
|
+ return 0;
|
|
}
|
|
- /* This should never get here. */
|
|
- return -IP_ERROR_INTERNAL;
|
|
+ if (res < 0)
|
|
+ return res;
|
|
+
|
|
+ int64_t frame_ts = -1;
|
|
+ if (priv->frame->pts >= 0)
|
|
+ frame_ts = priv->frame->pts;
|
|
+ else if (priv->frame->pkt_dts >= 0)
|
|
+ frame_ts = priv->frame->pkt_dts;
|
|
+
|
|
+ if (priv->seek_ts > 0 && (frame_ts >= 0 || priv->prev_frame_end >= 0)) {
|
|
+ if (ffmpeg_seek_into_frame(priv, frame_ts) == 0)
|
|
+ return 0;
|
|
+ priv->seek_ts = -1;
|
|
+ priv->prev_frame_end = -1;
|
|
+ }
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
|
|
+{
|
|
+ int res = swr_convert(priv->swr,
|
|
+ priv->swr_frame->extended_data,
|
|
+ /* TODO: proper buffer capacity */
|
|
+ priv->frame->nb_samples,
|
|
+ (const uint8_t **)priv->frame->extended_data,
|
|
+ priv->frame->nb_samples);
|
|
+ if (res >= 0) {
|
|
+ priv->swr_frame->nb_samples = res;
|
|
+ priv->swr_frame_start = 0;
|
|
+ }
|
|
+ return res;
|
|
}
|
|
|
|
static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
|
{
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
- struct ffmpeg_output *output = priv->output;
|
|
- int rc;
|
|
- int out_size;
|
|
-
|
|
- if (output->buffer_used_len == 0) {
|
|
- rc = ffmpeg_fill_buffer(ip_data,
|
|
- priv->input_context, priv->codec_context,
|
|
- priv->input, priv->output, priv->swr);
|
|
- if (rc <= 0)
|
|
- return rc;
|
|
+ int written = 0;
|
|
+ int res;
|
|
+
|
|
+ count /= sf_get_frame_size(ip_data->sf);
|
|
+
|
|
+ while (count) {
|
|
+ if (priv->swr_frame->nb_samples == 0) {
|
|
+ res = ffmpeg_get_frame(priv);
|
|
+ if (res == AVERROR_EOF)
|
|
+ break;
|
|
+ else if (res == 0)
|
|
+ continue;
|
|
+ else if (res < 0)
|
|
+ goto err;
|
|
+
|
|
+ res = ffmpeg_convert_frame(priv);
|
|
+ if (res < 0)
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ int copy_frames = min_i(count, priv->swr_frame->nb_samples);
|
|
+ int copy_bytes = copy_frames * sf_get_frame_size(ip_data->sf);
|
|
+ void *dst = priv->swr_frame->extended_data[0] + priv->swr_frame_start;
|
|
+ memcpy(buffer + written, dst, copy_bytes);
|
|
+
|
|
+ priv->swr_frame->nb_samples -= copy_frames;
|
|
+ priv->swr_frame_start += copy_bytes;
|
|
+ count -= copy_frames;
|
|
+ written += copy_bytes;
|
|
}
|
|
- out_size = min_i(output->buffer_used_len, count);
|
|
- memcpy(buffer, output->buffer_pos, out_size);
|
|
- output->buffer_used_len -= out_size;
|
|
- output->buffer_pos += out_size;
|
|
- return out_size;
|
|
+ return written;
|
|
+err:
|
|
+ d_print("%s\n", ffmpeg_errmsg(res));
|
|
+ return -IP_ERROR_INTERNAL;
|
|
}
|
|
|
|
static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
|
|
{
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
- AVStream *st = priv->input_context->streams[priv->input->stream_index];
|
|
- int ret;
|
|
+ AVStream *st = priv->format_ctx->streams[priv->stream_index];
|
|
|
|
- priv->input->seek_ts = offset * AV_TIME_BASE;
|
|
- priv->input->prev_frame_end = -1;
|
|
+ priv->seek_ts = offset * AV_TIME_BASE;
|
|
+ priv->prev_frame_end = -1;
|
|
int64_t ts = av_rescale(offset, st->time_base.den, st->time_base.num);
|
|
|
|
- avcodec_flush_buffers(priv->codec_context);
|
|
- /* TODO: also flush swresample buffers */
|
|
- /* Force reading a new packet in next ffmpeg_fill_buffer(). */
|
|
- priv->input->curr_pkt_size = 0;
|
|
-
|
|
- ret = avformat_seek_file(priv->input_context,
|
|
- priv->input->stream_index, 0, ts, ts, 0);
|
|
-
|
|
- if (ret < 0) {
|
|
+ int ret = avformat_seek_file(priv->format_ctx,
|
|
+ priv->stream_index, 0, ts, ts, 0);
|
|
+ if (ret < 0)
|
|
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
|
- } else {
|
|
- ffmpeg_buffer_flush(priv->output);
|
|
- return 0;
|
|
- }
|
|
+
|
|
+ priv->swr_frame->nb_samples = 0;
|
|
+ avcodec_flush_buffers(priv->codec_ctx);
|
|
+ /* also flush swresample buffers? */
|
|
+ return 0;
|
|
}
|
|
|
|
static void ffmpeg_read_metadata(struct growing_keyvals *c, AVDictionary *metadata)
|
|
@@ -445,7 +462,7 @@ static int ffmpeg_read_comments(struct input_plugin_data *ip_data,
|
|
struct keyval **comments)
|
|
{
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
- AVFormatContext *ic = priv->input_context;
|
|
+ AVFormatContext *ic = priv->format_ctx;
|
|
|
|
GROWING_KEYVALS(c);
|
|
|
|
@@ -463,29 +480,29 @@ static int ffmpeg_read_comments(struct input_plugin_data *ip_data,
|
|
static int ffmpeg_duration(struct input_plugin_data *ip_data)
|
|
{
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
- return priv->input_context->duration / AV_TIME_BASE;
|
|
+ return priv->format_ctx->duration / AV_TIME_BASE;
|
|
}
|
|
|
|
static long ffmpeg_bitrate(struct input_plugin_data *ip_data)
|
|
{
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
- long bitrate = priv->input_context->bit_rate;
|
|
+ long bitrate = priv->format_ctx->bit_rate;
|
|
return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
|
}
|
|
|
|
static long ffmpeg_current_bitrate(struct input_plugin_data *ip_data)
|
|
{
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
- AVStream *st = priv->input_context->streams[priv->input->stream_index];
|
|
+ AVStream *st = priv->format_ctx->streams[priv->stream_index];
|
|
long bitrate = -1;
|
|
/* ape codec returns silly numbers */
|
|
if (priv->codec->id == AV_CODEC_ID_APE)
|
|
return -1;
|
|
- if (priv->input->curr_duration > 0) {
|
|
- double seconds = priv->input->curr_duration * av_q2d(st->time_base);
|
|
- bitrate = (8 * priv->input->curr_size) / seconds;
|
|
- priv->input->curr_size = 0;
|
|
- priv->input->curr_duration = 0;
|
|
+ if (priv->curr_duration > 0) {
|
|
+ double seconds = priv->curr_duration * av_q2d(st->time_base);
|
|
+ bitrate = (8 * priv->curr_size) / seconds;
|
|
+ priv->curr_size = 0;
|
|
+ priv->curr_duration = 0;
|
|
}
|
|
return bitrate;
|
|
}
|
|
@@ -500,7 +517,7 @@ static char *ffmpeg_codec_profile(struct input_plugin_data *ip_data)
|
|
{
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
const char *profile;
|
|
- profile = av_get_profile_name(priv->codec, priv->codec_context->profile);
|
|
+ profile = av_get_profile_name(priv->codec, priv->codec_ctx->profile);
|
|
return profile ? xstrdup(profile) : NULL;
|
|
}
|
|
|
|
|
|
From 92580b01661a118ec2bf79c5f2097388e34c1fc2 Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Sun, 17 Aug 2025 14:28:46 +0300
|
|
Subject: [PATCH 05/13] Validate sample format in ip_open()
|
|
|
|
To prevent segfault in ip_setup() because channels=0, validate ip_data->sf
|
|
after opening ip.
|
|
---
|
|
input.c | 10 ++++++++++
|
|
1 file changed, 10 insertions(+)
|
|
|
|
diff --git a/input.c b/input.c
|
|
index c20cb3f6a..f5c5b3c24 100644
|
|
--- a/input.c
|
|
+++ b/input.c
|
|
@@ -605,6 +605,16 @@ int ip_open(struct input_plugin *ip)
|
|
ip_reset(ip, 1);
|
|
return rc;
|
|
}
|
|
+
|
|
+ unsigned bits = sf_get_bits(ip->data.sf);
|
|
+ unsigned channels = sf_get_channels(ip->data.sf);
|
|
+ unsigned rate = sf_get_rate(ip->data.sf);
|
|
+ if (!bits || !channels || !rate) {
|
|
+ d_print("corrupt file: bits = %u, channels = %u, rate = %u\n",
|
|
+ bits, channels, rate);
|
|
+ return -IP_ERROR_FILE_FORMAT;
|
|
+ }
|
|
+
|
|
ip->open = 1;
|
|
return 0;
|
|
}
|
|
|
|
From 4fab7c4246e7c99c0f867585282ff66a62af043b Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Sun, 17 Aug 2025 14:53:52 +0300
|
|
Subject: [PATCH 06/13] ip/ffmpeg: flush swresample buffer when seeking
|
|
|
|
---
|
|
ip/ffmpeg.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index 42f630ee7..775e7de1d 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -444,7 +444,7 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
|
|
|
|
priv->swr_frame->nb_samples = 0;
|
|
avcodec_flush_buffers(priv->codec_ctx);
|
|
- /* also flush swresample buffers? */
|
|
+ swr_convert(priv->swr, NULL, 0, NULL, 0); /* flush swr buffer */
|
|
return 0;
|
|
}
|
|
|
|
|
|
From 0192d8abd980a12eb438c425db60dcf4a353f0fc Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Sun, 17 Aug 2025 15:02:34 +0300
|
|
Subject: [PATCH 07/13] ip/ffmpeg: remember swr_frame's capacity
|
|
|
|
---
|
|
ip/ffmpeg.c | 5 +++--
|
|
1 file changed, 3 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index 775e7de1d..c659c1330 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -49,6 +49,7 @@ struct ffmpeg_private {
|
|
|
|
/* A buffer to hold swr_convert()-ed samples */
|
|
AVFrame *swr_frame;
|
|
+ int swr_frame_samples_cap;
|
|
int swr_frame_start;
|
|
|
|
/* Bitrate estimation */
|
|
@@ -213,6 +214,7 @@ static int ffmpeg_init_swr_frame(struct ffmpeg_private *priv,
|
|
d_print("av_frame_get_buffer(): %s\n", ffmpeg_errmsg(res));
|
|
return -IP_ERROR_INTERNAL;
|
|
}
|
|
+ priv->swr_frame_samples_cap = frame->nb_samples;
|
|
frame->nb_samples = 0;
|
|
|
|
priv->swr_frame = frame;
|
|
@@ -378,8 +380,7 @@ static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
|
|
{
|
|
int res = swr_convert(priv->swr,
|
|
priv->swr_frame->extended_data,
|
|
- /* TODO: proper buffer capacity */
|
|
- priv->frame->nb_samples,
|
|
+ priv->swr_frame_samples_cap,
|
|
(const uint8_t **)priv->frame->extended_data,
|
|
priv->frame->nb_samples);
|
|
if (res >= 0) {
|
|
|
|
From 02943fba13697749decff9375b89b9dfa05f04af Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Sun, 17 Aug 2025 15:54:19 +0300
|
|
Subject: [PATCH 08/13] ip/ffmpeg: reset swr_frame_start when seeking
|
|
|
|
---
|
|
ip/ffmpeg.c | 1 +
|
|
1 file changed, 1 insertion(+)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index c659c1330..71cc51116 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -444,6 +444,7 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
|
|
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
|
|
|
priv->swr_frame->nb_samples = 0;
|
|
+ priv->swr_frame_start = 0;
|
|
avcodec_flush_buffers(priv->codec_ctx);
|
|
swr_convert(priv->swr, NULL, 0, NULL, 0); /* flush swr buffer */
|
|
return 0;
|
|
|
|
From 608c80b3ce12fa535fdeb6ea4aebe851b433a70c Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Sun, 17 Aug 2025 17:27:20 +0300
|
|
Subject: [PATCH 09/13] ip/ffmpeg: better frame skipping logic
|
|
|
|
---
|
|
ip/ffmpeg.c | 82 ++++++++++++++++++++++++++---------------------------
|
|
1 file changed, 41 insertions(+), 41 deletions(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index 71cc51116..af6ecfb8d 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -44,8 +44,8 @@ struct ffmpeg_private {
|
|
|
|
AVPacket *pkt;
|
|
AVFrame *frame;
|
|
- int64_t seek_ts;
|
|
- int64_t prev_frame_end;
|
|
+ double seek_ts;
|
|
+ int64_t skip_samples;
|
|
|
|
/* A buffer to hold swr_convert()-ed samples */
|
|
AVFrame *swr_frame;
|
|
@@ -249,7 +249,6 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
|
|
priv.pkt = av_packet_alloc();
|
|
priv.frame = av_frame_alloc();
|
|
priv.seek_ts = -1;
|
|
- priv.prev_frame_end = -1;
|
|
|
|
priv.swr = swr_alloc();
|
|
ffmpeg_set_sf_and_swr_opts(priv.swr, priv.codec_ctx,
|
|
@@ -283,37 +282,37 @@ static int ffmpeg_close(struct input_plugin_data *ip_data)
|
|
return 0;
|
|
}
|
|
|
|
-/*
|
|
- * return:
|
|
- * 0 - retry
|
|
- * >0 - ok
|
|
- */
|
|
-static int ffmpeg_seek_into_frame(struct ffmpeg_private *priv, int64_t frame_ts)
|
|
+static int64_t ffmpeg_calc_skip_samples(struct ffmpeg_private *priv)
|
|
{
|
|
- if (frame_ts >= 0) {
|
|
- AVStream *s = priv->format_ctx->streams[priv->stream_index];
|
|
- frame_ts = av_rescale_q(frame_ts, s->time_base, AV_TIME_BASE_Q);
|
|
+ int64_t ts;
|
|
+ if (priv->frame->pts >= 0) {
|
|
+ ts = priv->frame->pts;
|
|
+ } else if (priv->frame->pkt_dts >= 0) {
|
|
+ ts = priv->frame->pkt_dts;
|
|
} else {
|
|
- frame_ts = priv->prev_frame_end;
|
|
+ d_print("AVFrame.pts and AVFrame.pkt_dts are unset\n");
|
|
+ return -1;
|
|
}
|
|
|
|
- if (frame_ts >= priv->seek_ts)
|
|
- return 1;
|
|
-
|
|
- int64_t frame_dur = av_rescale(priv->frame->nb_samples,
|
|
- AV_TIME_BASE, priv->frame->sample_rate);
|
|
- int64_t frame_end = frame_ts + frame_dur;
|
|
- priv->prev_frame_end = frame_end;
|
|
+ AVStream *s = priv->format_ctx->streams[priv->stream_index];
|
|
+ double frame_ts = ts * av_q2d(s->time_base);
|
|
|
|
- d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n",
|
|
- priv->seek_ts, frame_ts, frame_end);
|
|
+ d_print("seek_ts: %.6fs, frame_ts: %.6fs\n", priv->seek_ts, frame_ts);
|
|
|
|
- if (frame_end <= priv->seek_ts)
|
|
+ if (frame_ts >= priv->seek_ts)
|
|
return 0;
|
|
+ return (priv->seek_ts - frame_ts) * priv->frame->sample_rate;
|
|
+}
|
|
|
|
- int64_t skip_samples = av_rescale(priv->seek_ts - frame_ts,
|
|
- priv->frame->sample_rate, AV_TIME_BASE);
|
|
- priv->frame->nb_samples -= skip_samples;
|
|
+static void ffmpeg_skip_frame_part(struct ffmpeg_private *priv)
|
|
+{
|
|
+ if (priv->skip_samples >= priv->frame->nb_samples) {
|
|
+ d_print("skipping frame: %d samples\n",
|
|
+ priv->frame->nb_samples);
|
|
+ priv->skip_samples -= priv->frame->nb_samples;
|
|
+ priv->frame->nb_samples = 0;
|
|
+ return;
|
|
+ }
|
|
|
|
int bps = av_get_bytes_per_sample(priv->frame->format);
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
|
@@ -322,15 +321,17 @@ static int ffmpeg_seek_into_frame(struct ffmpeg_private *priv, int64_t frame_ts)
|
|
int channels = priv->codec_ctx->channels;
|
|
#endif
|
|
|
|
+ priv->frame->nb_samples -= priv->skip_samples;
|
|
+
|
|
/* Just modify frame's data pointer because it's throw-away */
|
|
if (av_sample_fmt_is_planar(priv->frame->format)) {
|
|
for (int i = 0; i < channels; i++)
|
|
- priv->frame->extended_data[i] += skip_samples * bps;
|
|
+ priv->frame->extended_data[i] += priv->skip_samples * bps;
|
|
} else {
|
|
- priv->frame->extended_data[0] += skip_samples * channels * bps;
|
|
+ priv->frame->extended_data[0] += priv->skip_samples * channels * bps;
|
|
}
|
|
- d_print("skipping %ld samples\n", skip_samples);
|
|
- return 1;
|
|
+ d_print("skipping %ld samples\n", priv->skip_samples);
|
|
+ priv->skip_samples = 0;
|
|
}
|
|
|
|
/*
|
|
@@ -361,17 +362,16 @@ static int ffmpeg_get_frame(struct ffmpeg_private *priv)
|
|
if (res < 0)
|
|
return res;
|
|
|
|
- int64_t frame_ts = -1;
|
|
- if (priv->frame->pts >= 0)
|
|
- frame_ts = priv->frame->pts;
|
|
- else if (priv->frame->pkt_dts >= 0)
|
|
- frame_ts = priv->frame->pkt_dts;
|
|
+ if (priv->seek_ts > 0) {
|
|
+ priv->skip_samples = ffmpeg_calc_skip_samples(priv);
|
|
+ if (priv->skip_samples >= 0)
|
|
+ priv->seek_ts = -1;
|
|
+ }
|
|
|
|
- if (priv->seek_ts > 0 && (frame_ts >= 0 || priv->prev_frame_end >= 0)) {
|
|
- if (ffmpeg_seek_into_frame(priv, frame_ts) == 0)
|
|
+ if (priv->skip_samples > 0) {
|
|
+ ffmpeg_skip_frame_part(priv);
|
|
+ if (priv->frame->nb_samples == 0)
|
|
return 0;
|
|
- priv->seek_ts = -1;
|
|
- priv->prev_frame_end = -1;
|
|
}
|
|
return 1;
|
|
}
|
|
@@ -434,8 +434,8 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
|
|
struct ffmpeg_private *priv = ip_data->private;
|
|
AVStream *st = priv->format_ctx->streams[priv->stream_index];
|
|
|
|
- priv->seek_ts = offset * AV_TIME_BASE;
|
|
- priv->prev_frame_end = -1;
|
|
+ priv->seek_ts = offset;
|
|
+ priv->skip_samples = 0;
|
|
int64_t ts = av_rescale(offset, st->time_base.den, st->time_base.num);
|
|
|
|
int ret = avformat_seek_file(priv->format_ctx,
|
|
|
|
From bb99dc0ccc57adb0559cee21c6bfca25e8e40834 Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Sun, 17 Aug 2025 19:22:50 +0300
|
|
Subject: [PATCH 10/13] ip/ffmpeg: don't process empty frames
|
|
|
|
---
|
|
ip/ffmpeg.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index af6ecfb8d..dd9061aba 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -356,7 +356,7 @@ static int ffmpeg_get_frame(struct ffmpeg_private *priv)
|
|
priv->curr_duration += priv->pkt->duration;
|
|
|
|
res = avcodec_send_packet(priv->codec_ctx, priv->pkt);
|
|
- if (res == AVERROR(EAGAIN))
|
|
+ if (res == 0 || res == AVERROR(EAGAIN))
|
|
return 0;
|
|
}
|
|
if (res < 0)
|
|
|
|
From 7e4935eee4668a8428a3e3bf73b866b09f3ed067 Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Mon, 18 Aug 2025 03:32:22 +0300
|
|
Subject: [PATCH 11/13] ip/ffmpeg: improve readability
|
|
|
|
Previously ffmpeg_read()'s while loop was kinda leaking into
|
|
ffmpeg_get_frame(), now it doesn't.
|
|
---
|
|
ip/ffmpeg.c | 36 ++++++++++++++++++++----------------
|
|
1 file changed, 20 insertions(+), 16 deletions(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index dd9061aba..fc748951f 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -337,30 +337,32 @@ static void ffmpeg_skip_frame_part(struct ffmpeg_private *priv)
|
|
/*
|
|
* return:
|
|
* <0 - error
|
|
- * 0 - retry
|
|
+ * 0 - eof
|
|
* >0 - ok
|
|
*/
|
|
static int ffmpeg_get_frame(struct ffmpeg_private *priv)
|
|
{
|
|
- int res = avcodec_receive_frame(priv->codec_ctx, priv->frame);
|
|
+ int res;
|
|
+retry:
|
|
+ res = avcodec_receive_frame(priv->codec_ctx, priv->frame);
|
|
if (res == AVERROR(EAGAIN)) {
|
|
av_packet_unref(priv->pkt);
|
|
res = av_read_frame(priv->format_ctx, priv->pkt);
|
|
if (res < 0)
|
|
- return res;
|
|
+ goto err;
|
|
|
|
if (priv->pkt->stream_index != priv->stream_index)
|
|
- return 0;
|
|
+ goto retry;
|
|
|
|
priv->curr_size += priv->pkt->size;
|
|
priv->curr_duration += priv->pkt->duration;
|
|
|
|
res = avcodec_send_packet(priv->codec_ctx, priv->pkt);
|
|
if (res == 0 || res == AVERROR(EAGAIN))
|
|
- return 0;
|
|
+ goto retry;
|
|
}
|
|
if (res < 0)
|
|
- return res;
|
|
+ goto err;
|
|
|
|
if (priv->seek_ts > 0) {
|
|
priv->skip_samples = ffmpeg_calc_skip_samples(priv);
|
|
@@ -371,9 +373,14 @@ static int ffmpeg_get_frame(struct ffmpeg_private *priv)
|
|
if (priv->skip_samples > 0) {
|
|
ffmpeg_skip_frame_part(priv);
|
|
if (priv->frame->nb_samples == 0)
|
|
- return 0;
|
|
+ goto retry;
|
|
}
|
|
return 1;
|
|
+err:
|
|
+ if (res == AVERROR_EOF)
|
|
+ return 0;
|
|
+ d_print("%s\n", ffmpeg_errmsg(res));
|
|
+ return -IP_ERROR_INTERNAL;
|
|
}
|
|
|
|
static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
|
|
@@ -386,8 +393,10 @@ static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
|
|
if (res >= 0) {
|
|
priv->swr_frame->nb_samples = res;
|
|
priv->swr_frame_start = 0;
|
|
+ return res;
|
|
}
|
|
- return res;
|
|
+ d_print("%s\n", ffmpeg_errmsg(res));
|
|
+ return -IP_ERROR_INTERNAL;
|
|
}
|
|
|
|
static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
|
@@ -401,16 +410,14 @@ static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int coun
|
|
while (count) {
|
|
if (priv->swr_frame->nb_samples == 0) {
|
|
res = ffmpeg_get_frame(priv);
|
|
- if (res == AVERROR_EOF)
|
|
+ if (res == 0)
|
|
break;
|
|
- else if (res == 0)
|
|
- continue;
|
|
else if (res < 0)
|
|
- goto err;
|
|
+ return res;
|
|
|
|
res = ffmpeg_convert_frame(priv);
|
|
if (res < 0)
|
|
- goto err;
|
|
+ return res;
|
|
}
|
|
|
|
int copy_frames = min_i(count, priv->swr_frame->nb_samples);
|
|
@@ -424,9 +431,6 @@ static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int coun
|
|
written += copy_bytes;
|
|
}
|
|
return written;
|
|
-err:
|
|
- d_print("%s\n", ffmpeg_errmsg(res));
|
|
- return -IP_ERROR_INTERNAL;
|
|
}
|
|
|
|
static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
|
|
|
|
From 23da0dbc7bf34655041195a7189a92b8018e9f12 Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Sun, 24 Aug 2025 19:16:57 +0300
|
|
Subject: [PATCH 12/13] ip/ffmpeg: fix building for ffmpeg 8.0
|
|
|
|
avcodec_close() can be safely removed because avcodec_free_context()
|
|
is its replacement since 2016. See ffmpeg commit 2ef6dab0a79
|
|
|
|
Builds with v3.3.9 v4.0.6 v6.1.3 v7.1.1 v8.0
|
|
---
|
|
ip/ffmpeg.c | 1 -
|
|
1 file changed, 1 deletion(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index fc748951f..2cb07671c 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -223,7 +223,6 @@ static int ffmpeg_init_swr_frame(struct ffmpeg_private *priv,
|
|
|
|
static void ffmpeg_free(struct ffmpeg_private *priv)
|
|
{
|
|
- avcodec_close(priv->codec_ctx);
|
|
avcodec_free_context(&priv->codec_ctx);
|
|
avformat_close_input(&priv->format_ctx);
|
|
|
|
|
|
From 625ea17808896daf2d02fa92b3766d10796209cb Mon Sep 17 00:00:00 2001
|
|
From: ihy123 <aladinandreyy@gmail.com>
|
|
Date: Mon, 25 Aug 2025 11:17:06 +0300
|
|
Subject: [PATCH 13/13] ip/ffmpeg: change sample format conversions
|
|
|
|
---
|
|
ip/ffmpeg.c | 10 ++++------
|
|
1 file changed, 4 insertions(+), 6 deletions(-)
|
|
|
|
diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
|
|
index 2cb07671c..2d3c610f2 100644
|
|
--- a/ip/ffmpeg.c
|
|
+++ b/ip/ffmpeg.c
|
|
@@ -157,13 +157,11 @@ static void ffmpeg_set_sf_and_swr_opts(SwrContext *swr, AVCodecContext *cc,
|
|
av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0);
|
|
av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0);
|
|
|
|
- *out_sample_fmt = cc->sample_fmt;
|
|
- switch (*out_sample_fmt) {
|
|
- case AV_SAMPLE_FMT_U8:
|
|
- sf |= sf_bits(8) | sf_signed(0);
|
|
- break;
|
|
- case AV_SAMPLE_FMT_S32:
|
|
+ switch (cc->sample_fmt) {
|
|
+ case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP:
|
|
+ case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P:
|
|
sf |= sf_bits(32) | sf_signed(1);
|
|
+ *out_sample_fmt = AV_SAMPLE_FMT_S32;
|
|
break;
|
|
default:
|
|
sf |= sf_bits(16) | sf_signed(1);
|