diff --git a/src/lib/ffmpeg-4.0/avcodec.pas b/src/lib/ffmpeg-4.0/avcodec.pas index 02c1bbef4..e7e567666 100644 --- a/src/lib/ffmpeg-4.0/avcodec.pas +++ b/src/lib/ffmpeg-4.0/avcodec.pas @@ -50,6 +50,7 @@ interface const FF_BUG_AUTODETECT = 1; + AV_PKT_DATA_SKIP_SAMPLES = 11; type TAVCodecID = ( AV_CODEC_ID_NONE @@ -58,6 +59,7 @@ interface TAVPicture = record do_not_instantiate_this_record: incomplete_record; end; + TAVPacketSideDataType = cenum; PAVPacket = ^TAVPacket; PPAVPacket = ^PAVPacket; TAVPacket = record @@ -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; @@ -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. diff --git a/src/lib/ffmpeg-5.0/avcodec.pas b/src/lib/ffmpeg-5.0/avcodec.pas index 8bba2fd72..c81a301d1 100644 --- a/src/lib/ffmpeg-5.0/avcodec.pas +++ b/src/lib/ffmpeg-5.0/avcodec.pas @@ -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 @@ -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; @@ -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. diff --git a/src/lib/ffmpeg-6.0/avcodec.pas b/src/lib/ffmpeg-6.0/avcodec.pas index 5a6091f80..334cea76f 100644 --- a/src/lib/ffmpeg-6.0/avcodec.pas +++ b/src/lib/ffmpeg-6.0/avcodec.pas @@ -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 @@ -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; @@ -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. diff --git a/src/lib/ffmpeg-7.0/avcodec.pas b/src/lib/ffmpeg-7.0/avcodec.pas index a3b6cc188..8e8845d2a 100644 --- a/src/lib/ffmpeg-7.0/avcodec.pas +++ b/src/lib/ffmpeg-7.0/avcodec.pas @@ -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 @@ -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; @@ -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. diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index a8d2e4443..71978d2f8 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -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(); @@ -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); @@ -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 @@ -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 @@ -759,6 +798,7 @@ function TFFmpegDecodeStream.ParseLoop(): boolean; begin Result := true; Packet := nil; + SeekCheckPTS := false; while LockParser() do begin @@ -832,6 +872,7 @@ function TFFmpegDecodeStream.ParseLoop(): boolean; fSeekRequest := false; SDL_CondBroadcast(SeekFinishedCond); + SeekCheckPTS := true; finally ResumeDecoderUnlocked(); SDL_UnlockMutex(fStateLock); @@ -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 @@ -986,6 +1037,7 @@ function TFFmpegDecodeStream.DecodeFrame(): integer; {$IFDEF DebugFFmpegDecode} TmpPos: double; {$ENDIF} + PTS: int64; begin Result := -1; Packet := nil; @@ -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) + '), ' +