Skip to content

Commit

Permalink
Merge pull request #954 from complexlogic/accurate_seek
Browse files Browse the repository at this point in the history
More Accurate FFmpeg Audio Seeking
  • Loading branch information
barbeque-squared authored Feb 4, 2025
2 parents 2603abc + 672433b commit 64ee846
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 17 deletions.
6 changes: 5 additions & 1 deletion src/lib/ffmpeg-4.0/avcodec.pas
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interface

const
FF_BUG_AUTODETECT = 1;
AV_PKT_DATA_SKIP_SAMPLES = 11;
type
TAVCodecID = (
AV_CODEC_ID_NONE
Expand All @@ -58,6 +59,7 @@ interface
TAVPicture = record
do_not_instantiate_this_record: incomplete_record;
end;
TAVPacketSideDataType = cenum;
PAVPacket = ^TAVPacket;
PPAVPacket = ^PAVPacket;
TAVPacket = record
Expand All @@ -69,7 +71,7 @@ TAVPacket = record
stream_index: cint;
flags: cint;
side_data: pointer;
we_do_not_use_side_data_elems: cint;
side_data_elems: cint;
we_do_not_use_duration: cint64;
we_do_not_use_pos: cint64;
we_do_not_use_convergence_duration: cint64;
Expand Down Expand Up @@ -328,5 +330,7 @@ function avcodec_parameters_to_context(codec: PAVCodecContext; par: PAVCodecPara
function av_packet_alloc(): PAVPacket; cdecl; external av__codec;
procedure av_packet_free(pkt: PPAVPacket); cdecl; external av__codec;
procedure av_packet_unref(pkt: PAVPacket); cdecl; external av__codec;
function av_packet_new_side_data(pkt: PAVPacket; type_data: TAVPacketSideDataType; size: csize_t): pcuint8; cdecl; external av__codec;
function av_packet_get_side_data(const pkt: PAVPacket; type_data: TAVPacketSideDataType; size: pcsize_t): pcuint8; cdecl; external av__codec;
implementation
end.
6 changes: 5 additions & 1 deletion src/lib/ffmpeg-5.0/avcodec.pas
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ interface

const
FF_BUG_AUTODETECT = 1;
AV_PKT_DATA_SKIP_SAMPLES = 11;
type
TAVCodecID = (
AV_CODEC_ID_NONE
);
TAVPacketSideDataType = cenum;
PAVPacket = ^TAVPacket;
PPAVPacket = ^PAVPacket;
TAVPacket = record
Expand All @@ -65,7 +67,7 @@ TAVPacket = record
stream_index: cint;
flags: cint;
we_do_not_use_side_data: pointer;
we_do_not_use_side_data_elems: cint;
side_data_elems: cint;
we_do_not_use_duration: cint64;
we_do_not_use_pos: cint64;
opaque: pointer;
Expand Down Expand Up @@ -288,5 +290,7 @@ procedure avcodec_free_context(avctx: PPAVCodecContext); cdecl; external av__cod
function avcodec_parameters_to_context(codec: PAVCodecContext; par: PAVCodecParameters): cint; cdecl; external av__codec;
function av_packet_alloc(): PAVPacket; cdecl; external av__codec;
procedure av_packet_free(pkt: PPAVPacket); cdecl; external av__codec;
function av_packet_new_side_data(pkt: PAVPacket; type_data: TAVPacketSideDataType; size: csize_t): pcuint8; cdecl; external av__codec;
function av_packet_get_side_data(const pkt: PAVPacket; type_data: TAVPacketSideDataType; size: pcsize_t): pcuint8; cdecl; external av__codec;
implementation
end.
6 changes: 5 additions & 1 deletion src/lib/ffmpeg-6.0/avcodec.pas
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ interface

const
FF_BUG_AUTODETECT = 1;
AV_PKT_DATA_SKIP_SAMPLES = 11;
type
TAVCodecID = (
AV_CODEC_ID_NONE
);
TAVPacketSideDataType = cenum;
PAVPacket = ^TAVPacket;
PPAVPacket = ^PAVPacket;
TAVPacket = record
Expand All @@ -65,7 +67,7 @@ TAVPacket = record
stream_index: cint;
flags: cint;
we_do_not_use_side_data: pointer;
we_do_not_use_side_data_elems: cint;
side_data_elems: cint;
we_do_not_use_duration: cint64;
we_do_not_use_pos: cint64;
opaque: pointer;
Expand Down Expand Up @@ -250,5 +252,7 @@ procedure avcodec_free_context(avctx: PPAVCodecContext); cdecl; external av__cod
function avcodec_parameters_to_context(codec: PAVCodecContext; par: PAVCodecParameters): cint; cdecl; external av__codec;
function av_packet_alloc(): PAVPacket; cdecl; external av__codec;
procedure av_packet_free(pkt: PPAVPacket); cdecl; external av__codec;
function av_packet_new_side_data(pkt: PAVPacket; type_data: TAVPacketSideDataType; size: csize_t): pcuint8; cdecl; external av__codec;
function av_packet_get_side_data(const pkt: PAVPacket; type_data: TAVPacketSideDataType; size: pcsize_t): pcuint8; cdecl; external av__codec;
implementation
end.
6 changes: 5 additions & 1 deletion src/lib/ffmpeg-7.0/avcodec.pas
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ interface

const
FF_BUG_AUTODETECT = 1;
AV_PKT_DATA_SKIP_SAMPLES = 11;
type
TAVCodecID = (
AV_CODEC_ID_NONE
);
TAVPacketSideDataType = cenum;
PAVPacket = ^TAVPacket;
PPAVPacket = ^PAVPacket;
TAVPacket = record
Expand All @@ -65,7 +67,7 @@ TAVPacket = record
stream_index: cint;
flags: cint;
we_do_not_use_side_data: pointer;
we_do_not_use_side_data_elems: cint;
side_data_elems: cint;
we_do_not_use_duration: cint64;
we_do_not_use_pos: cint64;
opaque: pointer;
Expand Down Expand Up @@ -242,5 +244,7 @@ procedure avcodec_free_context(avctx: PPAVCodecContext); cdecl; external av__cod
function avcodec_parameters_to_context(codec: PAVCodecContext; par: PAVCodecParameters): cint; cdecl; external av__codec;
function av_packet_alloc(): PAVPacket; cdecl; external av__codec;
procedure av_packet_free(pkt: PPAVPacket); cdecl; external av__codec;
function av_packet_new_side_data(pkt: PAVPacket; type_data: TAVPacketSideDataType; size: csize_t): pcuint8; cdecl; external av__codec;
function av_packet_get_side_data(const pkt: PAVPacket; type_data: TAVPacketSideDataType; size: pcsize_t): pcuint8; cdecl; external av__codec;
implementation
end.
84 changes: 71 additions & 13 deletions src/media/UAudioDecoder_FFmpeg.pas
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ TFFmpegDecodeStream = class(TAudioDecodeStream)
function IsSeeking(): boolean;
function IsQuit(): boolean;
function GetLoopInternal(): boolean;
procedure PutSkippedSamples(pkt: PAVPacket; DeltaPTS: int64);
function GetSkippedSamples(pkt: PAVPacket): int64;

procedure Reset();

Expand Down Expand Up @@ -614,6 +616,53 @@ function TFFmpegDecodeStream.GetLoopInternal(): boolean;
Result := fLoop;
end;

procedure TFFmpegDecodeStream.PutSkippedSamples(pkt: PAVPacket; DeltaPTS: int64);
var
data: pcuint8;
SkippedSamples: cuint32;
begin

// Calculate the number of samples for the decoder to skip based on the delta timestamp
SkippedSamples := Round(DeltaPTS * av_q2d(fAudioStream^.time_base) * fCodecCtx^.sample_rate);
data := av_packet_new_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, 10);
if (data = nil) then
Exit;

// FFmpeg's AVPacket side data uses a generic binary API. The required data format varies
// based on the side data type. The skip samples type is 10 bytes, formatted as follows:
// Number of samples to skip from beginning of packet: 32 bit unsigned integer, little endian
// Number of samples to skip from end of packet: 32 bit unsigned integer, little endian
// Reason for start skip (no format given): 8 bit unsigned integer
// Reason for end skip (0=padding silence, 1=convergence): 8 bit unsigned integer

{$IFDEF ENDIAN_BIG}
SkippedSamples := swap(SkippedSamples);
{$ENDIF}
puint32(data)^ := SkippedSamples;
puint32(data + 4)^ := 0;
(data + 8)^ := 0;
(data + 9)^ := 0;
// data is owned by FFmpeg (should not be freed by us)
end;

function TFFmpegDecodeStream.GetSkippedSamples(pkt: PAVPacket): int64;
var
size: csize_t;
data: pcuint8;
SkippedSamples: cuint32;
begin
Result := 0;
data := av_packet_get_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, @size);
if (data = nil) then
Exit;
SkippedSamples := pcuint32(data)^;
{$IFDEF ENDIAN_BIG}
SkippedSamples := swap(SkippedSamples);
{$ENDIF}
Result := Round(SkippedSamples / (av_q2d(fAudioStream^.time_base) * fCodecCtx^.sample_rate));
// data is owned by FFmpeg (should not be freed by us)
end;

function TFFmpegDecodeStream.GetLoop(): boolean;
begin
SDL_LockMutex(fStateLock);
Expand Down Expand Up @@ -662,26 +711,15 @@ procedure TFFmpegDecodeStream.SetPositionIntern(Time: real; Flush: boolean);
fErrorState := false;

// do not seek if we are already at the correct position.
// This is important especially for seeking to position 0 if we already are
// at the beginning. Although seeking with AVSEEK_FLAG_BACKWARD for pos 0 works,
// it is still a bit choppy (although much better than w/o AVSEEK_FLAG_BACKWARD).
if (Time = fAudioStreamPos) then
Exit;

// configure seek parameters
fSeekPos := Time;
fSeekFlush := Flush;
fSeekFlags := AVSEEK_FLAG_ANY;
fSeekFlags := AVSEEK_FLAG_BACKWARD;
fSeekRequest := true;

// Note: the BACKWARD-flag seeks to the first position <= the position
// searched for. Otherwise e.g. position 0 might not be seeked correct.
// For some reason ffmpeg sometimes doesn't use position 0 but the key-frame
// following. In streams with few key-frames (like many flv-files) the next
// key-frame after 0 might be 5secs ahead.
if (Time <= fAudioStreamPos) then
fSeekFlags := fSeekFlags or AVSEEK_FLAG_BACKWARD;

// send a reuse signal in case the parser was stopped (e.g. because of an EOF)
SDL_CondSignal(fParserIdleCond);
finally
Expand Down Expand Up @@ -734,6 +772,7 @@ function TFFmpegDecodeStream.ParseLoop(): boolean;
fileSize: integer;
urlError: integer;
errnum: integer;
SeekCheckPTS: boolean;

// Note: pthreads wakes threads waiting on a mutex in the order of their
// priority and not in FIFO order. SDL does not provide any option to
Expand All @@ -759,6 +798,7 @@ function TFFmpegDecodeStream.ParseLoop(): boolean;
begin
Result := true;
Packet := nil;
SeekCheckPTS := false;

while LockParser() do
begin
Expand Down Expand Up @@ -832,6 +872,7 @@ function TFFmpegDecodeStream.ParseLoop(): boolean;

fSeekRequest := false;
SDL_CondBroadcast(SeekFinishedCond);
SeekCheckPTS := true;
finally
ResumeDecoderUnlocked();
SDL_UnlockMutex(fStateLock);
Expand Down Expand Up @@ -886,6 +927,16 @@ function TFFmpegDecodeStream.ParseLoop(): boolean;

if (Packet^.stream_index = fAudioStreamIndex) then
begin
if (SeekCheckPTS) then
begin

// This is the first packet returned by the demuxer after a seek operation.
// If the packet timestamp is not exactly what was requested, then instruct
// the decoder to skip the extra samples in the output.
if (Packet^.pts < SeekTarget) then
PutSkippedSamples(Packet, SeekTarget - Packet^.pts);
SeekCheckPTS := false;
end;
fPacketQueue.Put(Packet);
Packet := nil;
end
Expand Down Expand Up @@ -986,6 +1037,7 @@ function TFFmpegDecodeStream.DecodeFrame(): integer;
{$IFDEF DebugFFmpegDecode}
TmpPos: double;
{$ENDIF}
PTS: int64;
begin
Result := -1;
Packet := nil;
Expand Down Expand Up @@ -1112,10 +1164,16 @@ function TFFmpegDecodeStream.DecodeFrame(): integer;
// if available, update the stream position to the presentation time of this package
if(Packet^.pts <> AV_NOPTS_VALUE) then
begin
PTS := Packet^.pts;

// If skipped samples are set due to a seek operation, take this into account when
// updating the stream position
if (Packet^.side_data_elems > 0) then
Inc(PTS, GetSkippedSamples(Packet));
{$IFDEF DebugFFmpegDecode}
TmpPos := fAudioStreamPos;
{$ENDIF}
fAudioStreamPos := av_q2d(fAudioStream^.time_base) * Packet^.pts;
fAudioStreamPos := av_q2d(fAudioStream^.time_base) * PTS;
{$IFDEF DebugFFmpegDecode}
DebugWriteln('Timestamp: ' + floattostrf(fAudioStreamPos, ffFixed, 15, 3) + ' ' +
'(Calc: ' + floattostrf(TmpPos, ffFixed, 15, 3) + '), ' +
Expand Down

0 comments on commit 64ee846

Please sign in to comment.