From f768ca3a2d20cc839e25ca3c95438909d54afd77 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Jun 2015 18:56:29 +0200 Subject: decoder/ffmpeg: move code to StreamRelativePts() --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'src/decoder') diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 722f954e2..586a1ef41 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -305,6 +305,22 @@ copy_interleave_frame(const AVCodecContext *codec_context, return data_size; } +/** + * Convert AVPacket::pts to a stream-relative time stamp (still in + * AVStream::time_base units). Returns a negative value on error. + */ +gcc_pure +static int64_t +StreamRelativePts(const AVPacket &packet, const AVStream &stream) +{ + auto pts = packet.pts; + if (pts < 0 || pts == int64_t(AV_NOPTS_VALUE)) + return -1; + + auto start = start_time_fallback(stream); + return pts - start; +} + static DecoderCommand ffmpeg_send_packet(Decoder &decoder, InputStream &is, const AVPacket *packet, @@ -313,12 +329,10 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, AVFrame *frame, uint8_t **buffer, int *buffer_size) { - if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) { - auto start = start_time_fallback(*stream); - if (packet->pts >= start) - decoder_timestamp(decoder, - time_from_ffmpeg(packet->pts - start, - stream->time_base)); + const auto pts = StreamRelativePts(*packet, *stream); + if (pts >= 0) { + decoder_timestamp(decoder, + time_from_ffmpeg(pts, stream->time_base)); } AVPacket packet2 = *packet; -- cgit v1.2.3 From d11e2724c4f1dbe3447b3e81d0a0e71bcc504a0b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Jun 2015 17:45:05 +0200 Subject: decoder/ffmpeg: use AVSEEK_FLAG_BACKWARD for seeking Ask FFmpeg to seek to the next packet boundary *before* the seek position, so we don't miss audio data. Now we get too much, but we'll solve that in the next commit. --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/decoder') diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 586a1ef41..f5c0e0a36 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -597,8 +597,12 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) av_stream->time_base) + start_time_fallback(*av_stream); + /* AVSEEK_FLAG_BACKWARD asks FFmpeg to seek to + the packet boundary before the seek time + stamp, not after */ + if (av_seek_frame(format_context, audio_stream, where, - AVSEEK_FLAG_ANY) < 0) + AVSEEK_FLAG_ANY|AVSEEK_FLAG_BACKWARD) < 0) decoder_seek_error(decoder); else { avcodec_flush_buffers(codec_context); -- cgit v1.2.3 From 327a8e6c5974f99ff1067afc2e725a2ebc2c4802 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Jun 2015 18:02:10 +0200 Subject: decoder/ffmpeg: skip unwanted samples after seeking When seeking to the beginning of a packet, skip the samples that come before the desired time stamp. --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 53 ++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) (limited to 'src/decoder') diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index f5c0e0a36..d5191a3c3 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -321,18 +321,44 @@ StreamRelativePts(const AVPacket &packet, const AVStream &stream) return pts - start; } +/** + * Convert a non-negative stream-relative time stamp in + * AVStream::time_base units to a PCM frame number. + */ +gcc_pure +static uint64_t +PtsToPcmFrame(uint64_t pts, const AVStream &stream, + const AVCodecContext &codec_context) +{ + return av_rescale_q(pts, stream.time_base, codec_context.time_base); +} + +/** + * @param min_frame skip all data before this PCM frame number; this + * is used after seeking to skip data in an AVPacket until the exact + * desired time stamp has been reached + */ static DecoderCommand ffmpeg_send_packet(Decoder &decoder, InputStream &is, const AVPacket *packet, AVCodecContext *codec_context, const AVStream *stream, AVFrame *frame, + uint64_t min_frame, size_t pcm_frame_size, uint8_t **buffer, int *buffer_size) { + size_t skip_bytes = 0; + const auto pts = StreamRelativePts(*packet, *stream); if (pts >= 0) { - decoder_timestamp(decoder, - time_from_ffmpeg(pts, stream->time_base)); + if (min_frame > 0) { + auto cur_frame = PtsToPcmFrame(pts, *stream, + *codec_context); + if (cur_frame < min_frame) + skip_bytes = pcm_frame_size * (min_frame - cur_frame); + } else + decoder_timestamp(decoder, + time_from_ffmpeg(pts, stream->time_base)); } AVPacket packet2 = *packet; @@ -368,8 +394,20 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, if (audio_size <= 0) continue; + const uint8_t *data = output_buffer; + if (skip_bytes > 0) { + if (skip_bytes >= size_t(audio_size)) { + skip_bytes -= audio_size; + continue; + } + + data += skip_bytes; + audio_size -= skip_bytes; + skip_bytes = 0; + } + cmd = decoder_data(decoder, is, - output_buffer, audio_size, + data, audio_size, codec_context->bit_rate / 1000); } return cmd; @@ -573,6 +611,8 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) uint8_t *interleaved_buffer = nullptr; int interleaved_buffer_size = 0; + uint64_t min_frame = 0; + DecoderCommand cmd; do { AVPacket packet; @@ -580,13 +620,15 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) /* end of file */ break; - if (packet.stream_index == audio_stream) + if (packet.stream_index == audio_stream) { cmd = ffmpeg_send_packet(decoder, input, &packet, codec_context, av_stream, frame, + min_frame, audio_format.GetFrameSize(), &interleaved_buffer, &interleaved_buffer_size); - else + min_frame = 0; + } else cmd = decoder_get_command(decoder); av_free_packet(&packet); @@ -606,6 +648,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) decoder_seek_error(decoder); else { avcodec_flush_buffers(codec_context); + min_frame = decoder_seek_where_frame(decoder); decoder_command_finished(decoder); } } -- cgit v1.2.3 From e4d0293a31f30c01142202ee433cd28115b73343 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 20 Jun 2015 16:06:01 +0200 Subject: DecoderAPI: "move" the Tag object Reduce runtime overhead. --- src/decoder/DecoderAPI.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/decoder') diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx index 4794d60e7..366c4af6d 100644 --- a/src/decoder/DecoderAPI.cxx +++ b/src/decoder/DecoderAPI.cxx @@ -566,7 +566,7 @@ decoder_tag(Decoder &decoder, InputStream *is, /* save the tag */ delete decoder.decoder_tag; - decoder.decoder_tag = new Tag(tag); + decoder.decoder_tag = new Tag(std::move(tag)); /* check for a new stream tag */ -- cgit v1.2.3 From 9acefcb256347ac252db3ea61be2e047d32c0a51 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 21 Jun 2015 15:02:14 +0200 Subject: DecoderThread: set Decoder::song_tag only for local files If the song tag comes from a stream, and MPD playback restarts, MPD would believe the tag should override the newly received tag. This makes the previous tag appear stuck. This change passes the song tag only if it's authoritative - i.e. if it's a song file. --- src/decoder/DecoderThread.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/decoder') diff --git a/src/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx index dd5518b98..b4362a548 100644 --- a/src/decoder/DecoderThread.cxx +++ b/src/decoder/DecoderThread.cxx @@ -380,7 +380,11 @@ decoder_run_song(DecoderControl &dc, const DetachedSong &song, const char *uri, Path path_fs) { Decoder decoder(dc, dc.start_time.IsPositive(), - new Tag(song.GetTag())); + /* pass the song tag only if it's + authoritative, i.e. if it's a local file - + tags on "stream" songs are just remembered + from the last time we played it*/ + song.IsFile() ? new Tag(song.GetTag()) : nullptr); int ret; dc.state = DecoderState::START; -- cgit v1.2.3 From 6d6f2746485d8234110858e5e3621b0ce03c6c8a Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 21 Jun 2015 15:09:50 +0200 Subject: DecoderAPI: discard unused song tag early If there's a stream tag, don't let the song tag override it in the next update_stream_tag() call. --- src/decoder/DecoderAPI.cxx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/decoder') diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx index 366c4af6d..941d3a70d 100644 --- a/src/decoder/DecoderAPI.cxx +++ b/src/decoder/DecoderAPI.cxx @@ -433,8 +433,11 @@ update_stream_tag(Decoder &decoder, InputStream *is) /* no stream tag present - submit the song tag instead */ - decoder.song_tag = nullptr; - } + } else + /* discard the song tag; we don't need it */ + delete decoder.song_tag; + + decoder.song_tag = nullptr; delete decoder.stream_tag; decoder.stream_tag = tag; -- cgit v1.2.3