From 2754f05b11e14e6ce98c7aa0dcb06fa583910f9d Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 30 Nov 2016 23:48:49 +0100 Subject: [PATCH 1/8] Move FFmpeg looping to ffmpeg_decoder for further changes (cleanup) --- src/coding/coding.h | 3 +++ src/coding/ffmpeg_decoder.c | 52 +++++++++++++++++++++++++++++++++++++ src/vgmstream.c | 48 +--------------------------------- 3 files changed, 56 insertions(+), 47 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index 02e1142f..6b8eeaff 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -111,6 +111,9 @@ void decode_at3plus(VGMSTREAM *vgmstream, #ifdef VGM_USE_FFMPEG void decode_ffmpeg(VGMSTREAM *stream, sample * outbuf, int32_t samples_to_do, int channels); + +void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample); + #endif void decode_acm(ACMStream * acm, sample * outbuf, diff --git a/src/coding/ffmpeg_decoder.c b/src/coding/ffmpeg_decoder.c index a6f0a5d6..eaecf32e 100644 --- a/src/coding/ffmpeg_decoder.c +++ b/src/coding/ffmpeg_decoder.c @@ -247,4 +247,56 @@ end: data->endOfAudio = endOfAudio; } + +void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) { + ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + int64_t ts; + +#ifndef VGM_USE_FFMPEG_ACCURATE_LOOPING + /* Seek to loop start by timestamp (closest frame) + adjust skipping some samples */ + /* FFmpeg seeks by ts by design (since not all containers can accurately skip to a frame). */ + /* TODO: this seems to be off by +-1 frames in some cases */ + ts = num_sample; + if (ts >= data->sampleRate * 2) { + data->samplesToDiscard = data->sampleRate * 2; + ts -= data->samplesToDiscard; + } + else { + data->samplesToDiscard = (int)ts; + ts = 0; + } + + /* todo fix this properly */ + if (data->totalFrames) { + data->framesRead = (int)ts; + ts = data->framesRead * (data->formatCtx->duration) / data->totalFrames; + } else { + data->samplesToDiscard = num_sample; + data->framesRead = 0; + ts = 0; + } + + avformat_seek_file(data->formatCtx, -1, ts - 1000, ts, ts, AVSEEK_FLAG_ANY); + avcodec_flush_buffers(data->codecCtx); +#endif /* ifndef VGM_USE_FFMPEG_ACCURATE_LOOPING */ + +#ifdef VGM_USE_FFMPEG_ACCURATE_LOOPING + /* Start from 0 and discard samples until loop_start for accurate looping (slower but not too noticeable) */ + /* We could also seek by offset (AVSEEK_FLAG_BYTE) to the frame closest to the loop then discard + * some samples, which is fast but would need calculations per format / when frame size is not constant */ + data->samplesToDiscard = num_sample; + data->framesRead = 0; + ts = 0; + + avformat_seek_file(data->formatCtx, -1, ts, ts, ts, AVSEEK_FLAG_ANY); + avcodec_flush_buffers(data->codecCtx); +#endif /* ifdef VGM_USE_FFMPEG_ACCURATE_LOOPING */ + + data->readNextPacket = 1; + data->bytesConsumedFromDecodedFrame = INT_MAX; + data->endOfStream = 0; + data->endOfAudio = 0; + +} + #endif diff --git a/src/vgmstream.c b/src/vgmstream.c index ef063f2f..f464e407 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -1789,53 +1789,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { } #ifdef VGM_USE_FFMPEG if (vgmstream->coding_type==coding_FFmpeg) { - ffmpeg_codec_data *data = (ffmpeg_codec_data *)(vgmstream->codec_data); - int64_t ts; - -#ifndef VGM_USE_FFMPEG_ACCURATE_LOOPING - /* Seek to loop start by timestamp (closest frame) + adjust skipping some samples */ - /* FFmpeg seeks by ts by design (since not all containers can accurately skip to a frame). */ - /* TODO: this seems to be off by +-1 frames in some cases */ - ts = vgmstream->loop_start_sample; - if (ts >= data->sampleRate * 2) { - data->samplesToDiscard = data->sampleRate * 2; - ts -= data->samplesToDiscard; - } - else { - data->samplesToDiscard = (int)ts; - ts = 0; - } - - /* todo fix this properly */ - if (data->totalFrames) { - data->framesRead = (int)ts; - ts = data->framesRead * (data->formatCtx->duration) / data->totalFrames; - } else { - data->samplesToDiscard = vgmstream->loop_start_sample; - data->framesRead = 0; - ts = 0; - } - - avformat_seek_file(data->formatCtx, -1, ts - 1000, ts, ts, AVSEEK_FLAG_ANY); - avcodec_flush_buffers(data->codecCtx); -#endif /* ifndef VGM_USE_FFMPEG_ACCURATE_LOOPING */ - -#ifdef VGM_USE_FFMPEG_ACCURATE_LOOPING - /* Start from 0 and discard samples until loop_start for accurate looping (slower but not too noticeable) */ - /* We could also seek by offset (AVSEEK_FLAG_BYTE) to the frame closest to the loop then discard - * some samples, which is fast but would need calculations per format / when frame size is not constant */ - data->samplesToDiscard = vgmstream->loop_start_sample; - data->framesRead = 0; - ts = 0; - - avformat_seek_file(data->formatCtx, -1, ts, ts, ts, AVSEEK_FLAG_ANY); - avcodec_flush_buffers(data->codecCtx); -#endif /* ifdef VGM_USE_FFMPEG_ACCURATE_LOOPING */ - - data->readNextPacket = 1; - data->bytesConsumedFromDecodedFrame = INT_MAX; - data->endOfStream = 0; - data->endOfAudio = 0; + seek_ffmpeg(vgmstream, vgmstream->loop_start_sample); } #endif /* VGM_USE_FFMPEG */ #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) From f5da8281e27e5170e67bfaa26acef7ed58376bd7 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 1 Dec 2016 19:58:51 +0100 Subject: [PATCH 2/8] Move FFmpeg reset to ffmpeg_decoder (cleanup) --- src/coding/coding.h | 6 +++--- src/coding/ffmpeg_decoder.c | 18 ++++++++++++++++++ src/vgmstream.c | 15 +-------------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index 6b8eeaff..400f2eb8 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -109,11 +109,11 @@ void decode_at3plus(VGMSTREAM *vgmstream, #endif #ifdef VGM_USE_FFMPEG -void decode_ffmpeg(VGMSTREAM *stream, - sample * outbuf, int32_t samples_to_do, int channels); +void decode_ffmpeg(VGMSTREAM *stream, sample * outbuf, int32_t samples_to_do, int channels); + +void reset_ffmpeg(VGMSTREAM *vgmstream); void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample); - #endif void decode_acm(ACMStream * acm, sample * outbuf, diff --git a/src/coding/ffmpeg_decoder.c b/src/coding/ffmpeg_decoder.c index eaecf32e..79f817ba 100644 --- a/src/coding/ffmpeg_decoder.c +++ b/src/coding/ffmpeg_decoder.c @@ -248,6 +248,24 @@ end: } +void reset_ffmpeg(VGMSTREAM *vgmstream) { + ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + + if (data->formatCtx) { + avformat_seek_file(data->formatCtx, -1, 0, 0, 0, AVSEEK_FLAG_ANY); + } + if (data->codecCtx) { + avcodec_flush_buffers(data->codecCtx); + } + data->readNextPacket = 1; + data->bytesConsumedFromDecodedFrame = INT_MAX; + data->framesRead = 0; + data->endOfStream = 0; + data->endOfAudio = 0; + data->samplesToDiscard = 0; +} + + void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) { ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; int64_t ts; diff --git a/src/vgmstream.c b/src/vgmstream.c index f464e407..56e1d42e 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -515,20 +515,7 @@ void reset_vgmstream(VGMSTREAM * vgmstream) { #ifdef VGM_USE_FFMPEG if (vgmstream->coding_type==coding_FFmpeg) { - ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; - - if (data->formatCtx) { - avformat_seek_file(data->formatCtx, -1, 0, 0, 0, AVSEEK_FLAG_ANY); - } - if (data->codecCtx) { - avcodec_flush_buffers(data->codecCtx); - } - data->readNextPacket = 1; - data->bytesConsumedFromDecodedFrame = INT_MAX; - data->framesRead = 0; - data->endOfStream = 0; - data->endOfAudio = 0; - data->samplesToDiscard = 0; + reset_ffmpeg(vgmstream); } #endif From 47be992b4b52baca2ba85274cdb7d711dde9fa98 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 1 Dec 2016 20:11:17 +0100 Subject: [PATCH 3/8] Fix for demuxers that can't seek to 0 (FFmpeg bugs, see init_seek) --- src/meta/ffmpeg.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/meta/ffmpeg.c b/src/meta/ffmpeg.c index 53ece35b..3564d57a 100644 --- a/src/meta/ffmpeg.c +++ b/src/meta/ffmpeg.c @@ -6,6 +6,9 @@ #define FFMPEG_DEFAULT_BLOCK_SIZE 2048 +static void init_seek(ffmpeg_codec_data * data); + + static volatile int g_ffmpeg_initialized = 0; /* @@ -328,6 +331,11 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of if (!data->sampleBuffer) goto fail; + + /* setup decent seeking for faulty formats */ + init_seek(data); + + return data; fail: @@ -337,6 +345,55 @@ fail: } +/** + * Special patching for FFmpeg's buggy seek code. + * + * To seek with avformat_seek_file/av_seek_frame, FFmpeg's demuxers can implement read_seek2 (newest API) + * or read_seek (older API), with various search modes. If none are available it will use seek_frame_generic, + * which manually reads frame by frame until the selected timestamp. However, the prev frame will be consumed + * (so after seeking to 0 next av_read_frame will actually give the second frame and so on). + * + * Fortunately seek_frame_generic can use an index to find the correct position. This function reads the + * first frame/packet and sets up index to timestamp 0. This ensures faulty demuxers will seek to 0 correctly. + */ +static void init_seek(ffmpeg_codec_data * data) { + int ret, found = 0; + int64_t ts = 0; + AVStream * stream; + AVPacket * pkt; + + /* read_seek wouldn't use the index, but direct access to FFmpeg's internals is no good */ + /* if (data->formatCtx->iformat->read_seek || data->formatCtx->iformat->read_seek2) + return; */ + + + pkt = data->lastReadPacket; + + /* find the first packet */ + while (!found) { + av_packet_unref(pkt); + ret = av_read_frame(data->formatCtx, pkt); + if (ret < 0) + break; + + if (pkt->stream_index != data->streamIndex) + continue; /* ignore non-selected streams */ + + found = 1; + } + if (!found) + return; + + /* add index 0 */ + stream = data->formatCtx->streams[data->streamIndex]; + av_add_index_entry(stream, pkt->pos, ts, pkt->size, 0, AVINDEX_KEYFRAME); /* todo distance*/ + + /* move back to beginning, since we just consumed packets */ + avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY); + avcodec_flush_buffers(data->codecCtx); +} + + void free_ffmpeg(ffmpeg_codec_data *data) { if (data->lastReadPacket) { av_packet_unref(data->lastReadPacket); From 609bfb5d61c964bb7df96a523bdfb6e892e5db6d Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 1 Dec 2016 23:49:00 +0100 Subject: [PATCH 4/8] Minor fixes and cleanup --- src/coding/ffmpeg_decoder.c | 56 ++++++++++++++++++------------------- src/meta/ffmpeg.c | 43 +++++++++++++++++++--------- 2 files changed, 58 insertions(+), 41 deletions(-) diff --git a/src/coding/ffmpeg_decoder.c b/src/coding/ffmpeg_decoder.c index 79f817ba..33af01c7 100644 --- a/src/coding/ffmpeg_decoder.c +++ b/src/coding/ffmpeg_decoder.c @@ -63,14 +63,13 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels) { ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + int bytesPerSample; + int bytesPerFrame; int frameSize; - int dataSize; int bytesToRead; int bytesRead; - int errcode; - uint8_t *targetBuf; AVFormatContext *formatCtx; @@ -78,25 +77,24 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, AVPacket *lastReadPacket; AVFrame *lastDecodedFrame; - int streamIndex; - int bytesConsumedFromDecodedFrame; int readNextPacket; int endOfStream; int endOfAudio; - int toConsume; - int framesReadNow; + + /* ignore decode attempts at EOF */ if ((data->totalFrames && data->framesRead >= data->totalFrames) || data->endOfStream || data->endOfAudio) { memset(outbuf, 0, samples_to_do * channels * sizeof(sample)); return; } - frameSize = data->channels * (data->bitsPerSample / 8); - dataSize = 0; + bytesPerSample = data->bitsPerSample / 8; + bytesPerFrame = channels * bytesPerSample; + frameSize = data->channels * bytesPerSample; bytesToRead = samples_to_do * frameSize; bytesRead = 0; @@ -109,8 +107,6 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, lastReadPacket = data->lastReadPacket; lastDecodedFrame = data->lastDecodedFrame; - streamIndex = data->streamIndex; - bytesConsumedFromDecodedFrame = data->bytesConsumedFromDecodedFrame; readNextPacket = data->readNextPacket; @@ -120,15 +116,18 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, /* keep reading and decoding packets until the requested number of samples (in bytes) */ while (bytesRead < bytesToRead) { int planeSize; - int planar = av_sample_fmt_is_planar(codecCtx->sample_fmt); - dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, - lastDecodedFrame->nb_samples, - codecCtx->sample_fmt, 1); - + int planar; + int dataSize; + int toConsume; + int errcode; + + + /* size of previous frame */ + dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, lastDecodedFrame->nb_samples, codecCtx->sample_fmt, 1); if (dataSize < 0) dataSize = 0; - /* read packet */ + /* read new frame + packets when requested */ while (readNextPacket && !endOfAudio) { if (!endOfStream) { av_packet_unref(lastReadPacket); @@ -139,10 +138,11 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, if (formatCtx->pb && formatCtx->pb->error) break; } - if (lastReadPacket->stream_index != streamIndex) - continue; /* ignore non audio streams */ + if (lastReadPacket->stream_index != data->streamIndex) + continue; /* ignore non-selected streams */ } + /* send compressed packet to decoder (NULL at EOF to "drain") */ if ((errcode = avcodec_send_packet(codecCtx, endOfStream ? NULL : lastReadPacket)) < 0) { if (errcode != AVERROR(EAGAIN)) { goto end; @@ -152,13 +152,14 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, readNextPacket = 0; } - /* decode packet */ + /* decode packets into frame (checking if we have bytes to consume from previous frame) */ if (dataSize <= bytesConsumedFromDecodedFrame) { if (endOfStream && endOfAudio) break; bytesConsumedFromDecodedFrame = 0; + /* receive uncompressed data from decoder */ if ((errcode = avcodec_receive_frame(codecCtx, lastDecodedFrame)) < 0) { if (errcode == AVERROR_EOF) { endOfAudio = 1; @@ -173,18 +174,17 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, } } + /* size of current frame */ dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, lastDecodedFrame->nb_samples, codecCtx->sample_fmt, 1); - if (dataSize < 0) dataSize = 0; } toConsume = FFMIN((dataSize - bytesConsumedFromDecodedFrame), (bytesToRead - bytesRead)); - /* discard packet if needed (fully or partially) */ + /* discard decoded frame if needed (fully or partially) */ if (data->samplesToDiscard) { int samplesToConsume; - int bytesPerFrame = ((data->bitsPerSample / 8) * channels); /* discard all if there are more samples to do than the packet's samples */ if (data->samplesToDiscard >= dataSize / bytesPerFrame) { @@ -206,13 +206,13 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, } } - /* copy packet to buffer (mux channels if needed) */ + /* copy decoded frame to buffer (mux channels if needed) */ + planar = av_sample_fmt_is_planar(codecCtx->sample_fmt); if (!planar || channels == 1) { memmove(targetBuf + bytesRead, (lastDecodedFrame->data[0] + bytesConsumedFromDecodedFrame), toConsume); } else { uint8_t * out = (uint8_t *) targetBuf + bytesRead; - int bytesPerSample = data->bitsPerSample / 8; int bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / channels; int toConsumePerPlane = toConsume / channels; int s, ch; @@ -252,7 +252,7 @@ void reset_ffmpeg(VGMSTREAM *vgmstream) { ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; if (data->formatCtx) { - avformat_seek_file(data->formatCtx, -1, 0, 0, 0, AVSEEK_FLAG_ANY); + avformat_seek_file(data->formatCtx, data->streamIndex, 0, 0, 0, AVSEEK_FLAG_ANY); } if (data->codecCtx) { avcodec_flush_buffers(data->codecCtx); @@ -294,7 +294,7 @@ void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) { ts = 0; } - avformat_seek_file(data->formatCtx, -1, ts - 1000, ts, ts, AVSEEK_FLAG_ANY); + avformat_seek_file(data->formatCtx, data->streamIndex, ts - 1000, ts, ts, AVSEEK_FLAG_ANY); avcodec_flush_buffers(data->codecCtx); #endif /* ifndef VGM_USE_FFMPEG_ACCURATE_LOOPING */ @@ -306,7 +306,7 @@ void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) { data->framesRead = 0; ts = 0; - avformat_seek_file(data->formatCtx, -1, ts, ts, ts, AVSEEK_FLAG_ANY); + avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY); avcodec_flush_buffers(data->codecCtx); #endif /* ifdef VGM_USE_FFMPEG_ACCURATE_LOOPING */ diff --git a/src/meta/ffmpeg.c b/src/meta/ffmpeg.c index 3564d57a..94c96836 100644 --- a/src/meta/ffmpeg.c +++ b/src/meta/ffmpeg.c @@ -4,7 +4,9 @@ #ifdef VGM_USE_FFMPEG +/* internal sizes, can be any value */ #define FFMPEG_DEFAULT_BLOCK_SIZE 2048 +#define FFMPEG_DEFAULT_IO_BUFFER_SIZE 128 * 1024 static void init_seek(ffmpeg_codec_data * data); @@ -57,10 +59,19 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_FFmpeg; + /* this may happen for some streams */ + if (vgmstream->num_samples <= 0) + goto fail; + + return vgmstream; fail: free_ffmpeg(data); + if (vgmstream) { + vgmstream->codec_data = NULL; + close_vgmstream(vgmstream); + } return NULL; } @@ -73,10 +84,9 @@ static int ffmpeg_read(void *opaque, uint8_t *buf, int buf_size) { ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque; uint64_t offset = data->offset; - int max_to_copy; + int max_to_copy = 0; int ret; if (data->header_insert_block) { - max_to_copy = 0; if (offset < data->header_size) { max_to_copy = (int)(data->header_size - offset); if (max_to_copy > buf_size) { @@ -125,7 +135,11 @@ static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) return data->size + data->header_size; } whence &= ~(AVSEEK_SIZE | AVSEEK_FORCE); + /* false offsets, on reads data->start will be added */ switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: offset += data->offset; break; @@ -162,6 +176,7 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of int errcode, i; int streamIndex; + AVStream *stream; AVCodecParameters *codecPar; AVRational tb; @@ -219,10 +234,10 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of /* setup IO, attempt to autodetect format and gather some info */ - data->buffer = av_malloc(128 * 1024); + data->buffer = av_malloc(FFMPEG_DEFAULT_IO_BUFFER_SIZE); if (!data->buffer) goto fail; - data->ioCtx = avio_alloc_context(data->buffer, 128 * 1024, 0, data, ffmpeg_read, ffmpeg_write, ffmpeg_seek); + data->ioCtx = avio_alloc_context(data->buffer, FFMPEG_DEFAULT_IO_BUFFER_SIZE, 0, data, ffmpeg_read, ffmpeg_write, ffmpeg_seek); if (!data->ioCtx) goto fail; data->formatCtx = avformat_alloc_context(); @@ -239,16 +254,19 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of streamIndex = -1; for (i = 0; i < data->formatCtx->nb_streams; ++i) { - codecPar = data->formatCtx->streams[i]->codecpar; - if (codecPar->codec_type == AVMEDIA_TYPE_AUDIO) { - streamIndex = i; - break; + stream = data->formatCtx->streams[i]; + codecPar = stream->codecpar; + if (streamIndex < 0 && codecPar->codec_type == AVMEDIA_TYPE_AUDIO) { + streamIndex = i; /* select first audio stream found */ + } else { + stream->discard = AVDISCARD_ALL; /* disable demuxing unneded streams */ } } if (streamIndex < 0) goto fail; data->streamIndex = streamIndex; + stream = data->formatCtx->streams[streamIndex]; /* prepare codec and frame/packet buffers */ @@ -257,7 +275,7 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of if ((errcode = avcodec_parameters_to_context(data->codecCtx, codecPar)) < 0) goto fail; - av_codec_set_pkt_timebase(data->codecCtx, data->formatCtx->streams[streamIndex]->time_base); + av_codec_set_pkt_timebase(data->codecCtx, stream->time_base); data->codec = avcodec_find_decoder(data->codecCtx->codec_id); if (!data->codec) goto fail; @@ -320,10 +338,9 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of /* try to guess frames/samples (duration isn't always set) */ tb.num = 1; tb.den = data->codecCtx->sample_rate; - data->totalFrames = av_rescale_q(data->formatCtx->streams[streamIndex]->duration, data->formatCtx->streams[streamIndex]->time_base, tb); + data->totalFrames = av_rescale_q(stream->duration, stream->time_base, tb); if (data->totalFrames < 0) - data->totalFrames = 0; - + data->totalFrames = 0; /* caller must consider this */ /* setup decode buffer */ data->samplesPerBlock = FFMPEG_DEFAULT_BLOCK_SIZE; @@ -386,7 +403,7 @@ static void init_seek(ffmpeg_codec_data * data) { /* add index 0 */ stream = data->formatCtx->streams[data->streamIndex]; - av_add_index_entry(stream, pkt->pos, ts, pkt->size, 0, AVINDEX_KEYFRAME); /* todo distance*/ + av_add_index_entry(stream, pkt->pos, ts, pkt->size, 0, AVINDEX_KEYFRAME); /* move back to beginning, since we just consumed packets */ avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY); From 8cdce2c196987a9ba82e43b07eb6474ced361baa Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 2 Dec 2016 22:33:51 +0100 Subject: [PATCH 5/8] Check on init if seek to 0 works (buggier demuxers may fail) --- src/meta/ffmpeg.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/meta/ffmpeg.c b/src/meta/ffmpeg.c index 94c96836..c4fef130 100644 --- a/src/meta/ffmpeg.c +++ b/src/meta/ffmpeg.c @@ -8,7 +8,7 @@ #define FFMPEG_DEFAULT_BLOCK_SIZE 2048 #define FFMPEG_DEFAULT_IO_BUFFER_SIZE 128 * 1024 -static void init_seek(ffmpeg_codec_data * data); +static int init_seek(ffmpeg_codec_data * data); static volatile int g_ffmpeg_initialized = 0; @@ -350,7 +350,8 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of /* setup decent seeking for faulty formats */ - init_seek(data); + errcode = init_seek(data); + if (errcode < 0) goto fail; return data; @@ -372,8 +373,9 @@ fail: * * Fortunately seek_frame_generic can use an index to find the correct position. This function reads the * first frame/packet and sets up index to timestamp 0. This ensures faulty demuxers will seek to 0 correctly. + * Some formats may not seek to 0 even with this though. */ -static void init_seek(ffmpeg_codec_data * data) { +static int init_seek(ffmpeg_codec_data * data) { int ret, found = 0; int64_t ts = 0; AVStream * stream; @@ -399,15 +401,22 @@ static void init_seek(ffmpeg_codec_data * data) { found = 1; } if (!found) - return; + return -1; /* add index 0 */ stream = data->formatCtx->streams[data->streamIndex]; - av_add_index_entry(stream, pkt->pos, ts, pkt->size, 0, AVINDEX_KEYFRAME); + ret = av_add_index_entry(stream, pkt->pos, ts, pkt->size, 0, AVINDEX_KEYFRAME); + if ( ret < 0 ) + return ret; /* move back to beginning, since we just consumed packets */ - avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY); + ret = avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY); + if ( ret < 0 ) + return ret; /* we can't even reset_vgmstream the file */ + avcodec_flush_buffers(data->codecCtx); + + return 0; } From 80c8791288c9c21b02a4ac301d6017f4aebe6bca Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 3 Dec 2016 01:56:27 +0100 Subject: [PATCH 6/8] Improve init_seek: don't add existing index, find packet size (for M4A) --- src/meta/ffmpeg.c | 53 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/src/meta/ffmpeg.c b/src/meta/ffmpeg.c index c4fef130..45b38bfa 100644 --- a/src/meta/ffmpeg.c +++ b/src/meta/ffmpeg.c @@ -373,43 +373,58 @@ fail: * * Fortunately seek_frame_generic can use an index to find the correct position. This function reads the * first frame/packet and sets up index to timestamp 0. This ensures faulty demuxers will seek to 0 correctly. - * Some formats may not seek to 0 even with this though. + * Some formats may not seek to 0 even with this, though. */ static int init_seek(ffmpeg_codec_data * data) { - int ret, found = 0; + int ret, ts_index, found_first = 0; int64_t ts = 0; + int64_t pos; /* offset */ + int size; /* coded size */ + int distance = 0; /* always? */ + AVStream * stream; AVPacket * pkt; - /* read_seek wouldn't use the index, but direct access to FFmpeg's internals is no good */ - /* if (data->formatCtx->iformat->read_seek || data->formatCtx->iformat->read_seek2) - return; */ - - + stream = data->formatCtx->streams[data->streamIndex]; pkt = data->lastReadPacket; - /* find the first packet */ - while (!found) { + /* read_seek shouldn't need this index, but direct access to FFmpeg's internals is no good */ + /* if (data->formatCtx->iformat->read_seek || data->formatCtx->iformat->read_seek2) + return 0; */ + + /* some formats already have a proper index (e.g. M4A) */ + ts_index = av_index_search_timestamp(stream, ts, AVSEEK_FLAG_ANY); + if (ts_index>=0) + goto test_seek; + + + /* find the first + second packets to get pos/size */ + while (1) { av_packet_unref(pkt); ret = av_read_frame(data->formatCtx, pkt); if (ret < 0) - break; - + goto fail; if (pkt->stream_index != data->streamIndex) continue; /* ignore non-selected streams */ - found = 1; + if (!found_first) { /* first found */ + found_first = 1; + pos = pkt->pos; + continue; + } else { /* second found */ + size = pkt->pos - pos; /* coded, pkt->size is decoded size */ + break; + } } - if (!found) - return -1; /* add index 0 */ - stream = data->formatCtx->streams[data->streamIndex]; - ret = av_add_index_entry(stream, pkt->pos, ts, pkt->size, 0, AVINDEX_KEYFRAME); + ret = av_add_index_entry(stream, pos, ts, size, distance, AVINDEX_KEYFRAME); if ( ret < 0 ) return ret; - /* move back to beginning, since we just consumed packets */ + +test_seek: + /* seek to 0 test / move back to beginning, since we just consumed packets */ ret = avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY); if ( ret < 0 ) return ret; /* we can't even reset_vgmstream the file */ @@ -417,6 +432,10 @@ static int init_seek(ffmpeg_codec_data * data) { avcodec_flush_buffers(data->codecCtx); return 0; + + +fail: + return -1; } From 0faa3286aad09cb8aa5737c0f9d4f4e5ba8edc63 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 3 Dec 2016 11:42:38 +0100 Subject: [PATCH 7/8] Don't manually check framesRead and rely on FFmpeg's EOFs FFmpeg's duration isn't always reliable (ie. bad headers) and the decoder detects EOFs already, extra decoding attempts should be ignored. This way vgmstream can use other values without modifying ffmpeg_codec_data's state. --- src/coding/ffmpeg_decoder.c | 16 +++------------- src/meta/ffmpeg.c | 1 - src/vgmstream.h | 1 - 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/coding/ffmpeg_decoder.c b/src/coding/ffmpeg_decoder.c index 33af01c7..345be981 100644 --- a/src/coding/ffmpeg_decoder.c +++ b/src/coding/ffmpeg_decoder.c @@ -59,8 +59,7 @@ static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount, } } -void decode_ffmpeg(VGMSTREAM *vgmstream, - sample * outbuf, int32_t samples_to_do, int channels) { +void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels) { ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; int bytesPerSample; @@ -87,7 +86,7 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, /* ignore decode attempts at EOF */ - if ((data->totalFrames && data->framesRead >= data->totalFrames) || data->endOfStream || data->endOfAudio) { + if (data->endOfStream || data->endOfAudio) { memset(outbuf, 0, samples_to_do * channels * sizeof(sample)); return; } @@ -231,11 +230,6 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, end: framesReadNow = bytesRead / frameSize; - if (data->totalFrames && (data->framesRead + framesReadNow > data->totalFrames)) { - framesReadNow = (int)(data->totalFrames - data->framesRead); - } - - data->framesRead += framesReadNow; // Convert the audio convert_audio(outbuf, data->sampleBuffer, framesReadNow * channels, data->bitsPerSample, data->floatingPoint); @@ -259,7 +253,6 @@ void reset_ffmpeg(VGMSTREAM *vgmstream) { } data->readNextPacket = 1; data->bytesConsumedFromDecodedFrame = INT_MAX; - data->framesRead = 0; data->endOfStream = 0; data->endOfAudio = 0; data->samplesToDiscard = 0; @@ -286,11 +279,9 @@ void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) { /* todo fix this properly */ if (data->totalFrames) { - data->framesRead = (int)ts; - ts = data->framesRead * (data->formatCtx->duration) / data->totalFrames; + ts = (int)ts * (data->formatCtx->duration) / data->totalFrames; } else { data->samplesToDiscard = num_sample; - data->framesRead = 0; ts = 0; } @@ -303,7 +294,6 @@ void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) { /* We could also seek by offset (AVSEEK_FLAG_BYTE) to the frame closest to the loop then discard * some samples, which is fast but would need calculations per format / when frame size is not constant */ data->samplesToDiscard = num_sample; - data->framesRead = 0; ts = 0; avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY); diff --git a/src/meta/ffmpeg.c b/src/meta/ffmpeg.c index 45b38bfa..e77c0c60 100644 --- a/src/meta/ffmpeg.c +++ b/src/meta/ffmpeg.c @@ -332,7 +332,6 @@ ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_of } data->bitrate = (int)(data->codecCtx->bit_rate); - data->framesRead = 0; data->endOfStream = 0; data->endOfAudio = 0; diff --git a/src/vgmstream.h b/src/vgmstream.h index 1a71487a..483105fd 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -870,7 +870,6 @@ typedef struct { int floatingPoint; int sampleRate; int64_t totalFrames; // sample count, or 0 if unknown - int64_t framesRead; int bitrate; // Intermediate buffer From c5c97c4027bf2c3ec8526c7513a29097ac75959c Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 3 Dec 2016 18:17:37 +0100 Subject: [PATCH 8/8] Fixed discard in edge cases --- src/coding/ffmpeg_decoder.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/coding/ffmpeg_decoder.c b/src/coding/ffmpeg_decoder.c index 345be981..4abc52e0 100644 --- a/src/coding/ffmpeg_decoder.c +++ b/src/coding/ffmpeg_decoder.c @@ -183,25 +183,25 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, /* discard decoded frame if needed (fully or partially) */ if (data->samplesToDiscard) { - int samplesToConsume; + int samplesDataSize = dataSize / bytesPerFrame; - /* discard all if there are more samples to do than the packet's samples */ - if (data->samplesToDiscard >= dataSize / bytesPerFrame) { - samplesToConsume = dataSize / bytesPerFrame; - } - else { - samplesToConsume = toConsume / bytesPerFrame; - } + if (data->samplesToDiscard >= samplesDataSize) { + /* discard all of the frame's samples and continue to the next */ - if (data->samplesToDiscard >= samplesToConsume) { /* full discard: skip to next */ - data->samplesToDiscard -= samplesToConsume; bytesConsumedFromDecodedFrame = dataSize; + data->samplesToDiscard -= samplesDataSize; + continue; } - else { /* partial discard: copy below */ - bytesConsumedFromDecodedFrame += data->samplesToDiscard * bytesPerFrame; - toConsume -= data->samplesToDiscard * bytesPerFrame; + else { + /* discard part of the frame and copy the rest below */ + int bytesToDiscard = data->samplesToDiscard * bytesPerFrame; + int dataSizeLeft = dataSize - bytesToDiscard; + + bytesConsumedFromDecodedFrame += bytesToDiscard; data->samplesToDiscard = 0; + if (toConsume > dataSizeLeft) + toConsume = dataSizeLeft; /* consume at most dataSize left */ } }