mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-24 15:00:11 +01:00
Move FFmpeg code from ffmpeg.c to ffmpeg_decoder.c for consistency
This commit is contained in:
parent
25e9d1bcde
commit
4263533ba9
@ -204,9 +204,13 @@ void free_at3plus(maiatrac3plus_codec_data *data);
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
/* ffmpeg_decoder */
|
||||
ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
|
||||
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size);
|
||||
|
||||
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);
|
||||
void free_ffmpeg(ffmpeg_codec_data *data);
|
||||
|
||||
void ffmpeg_set_skip_samples(ffmpeg_codec_data * data, int skip_samples);
|
||||
#endif
|
||||
|
@ -1,7 +1,34 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "coding.h"
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
/* internal sizes, can be any value */
|
||||
#define FFMPEG_DEFAULT_BUFFER_SIZE 2048
|
||||
#define FFMPEG_DEFAULT_IO_BUFFER_SIZE 128 * 1024
|
||||
|
||||
|
||||
static volatile int g_ffmpeg_initialized = 0;
|
||||
|
||||
|
||||
/* ******************************************** */
|
||||
/* INTERNAL UTILS */
|
||||
/* ******************************************** */
|
||||
|
||||
/* Global FFmpeg init */
|
||||
static void g_init_ffmpeg() {
|
||||
if (g_ffmpeg_initialized == 1) {
|
||||
while (g_ffmpeg_initialized < 2); /* active wait for lack of a better way */
|
||||
}
|
||||
else if (g_ffmpeg_initialized == 0) {
|
||||
g_ffmpeg_initialized = 1;
|
||||
av_log_set_flags(AV_LOG_SKIP_REPEATED);
|
||||
av_log_set_level(AV_LOG_ERROR);
|
||||
av_register_all();
|
||||
g_ffmpeg_initialized = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* converts codec's samples (can be in any format, ex. Ogg's float32) to PCM16 */
|
||||
static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount, int bitsPerSample, int floatingPoint) {
|
||||
int s;
|
||||
switch (bitsPerSample) {
|
||||
@ -59,15 +86,390 @@ static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Some formats may not seek to 0 even with this, though.
|
||||
*/
|
||||
static int init_seek(ffmpeg_codec_data * data) {
|
||||
int ret, ts_index, found_first = 0;
|
||||
int64_t ts = 0;
|
||||
int64_t pos = 0; /* offset */
|
||||
int size = 0; /* coded size */
|
||||
int distance = 0; /* always? */
|
||||
|
||||
AVStream * stream;
|
||||
AVPacket * pkt;
|
||||
|
||||
stream = data->formatCtx->streams[data->streamIndex];
|
||||
pkt = data->lastReadPacket;
|
||||
|
||||
/* 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;
|
||||
if (pkt->stream_index != data->streamIndex)
|
||||
continue; /* ignore non-selected streams */
|
||||
|
||||
if (!found_first) { /* first found */
|
||||
found_first = 1;
|
||||
pos = pkt->pos;
|
||||
ts = pkt->dts;
|
||||
continue;
|
||||
} else { /* second found */
|
||||
size = pkt->pos - pos; /* coded, pkt->size is decoded size */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_first)
|
||||
goto fail;
|
||||
|
||||
/* in rare cases there is only one packet */
|
||||
/* if (size == 0) { size = data_end - pos; } */ /* no easy way to know, ignore (most formats don's need size) */
|
||||
|
||||
/* some formats (XMA1) don't seem to have packet.dts, pretend it's 0 */
|
||||
if (ts == INT64_MIN)
|
||||
ts = 0;
|
||||
|
||||
/* Some streams start with negative DTS (observed in Ogg). For Ogg seeking to negative or 0 doesn't alter the output.
|
||||
* It does seem seeking before decoding alters a bunch of (inaudible) +-1 lower bytes though. */
|
||||
VGM_ASSERT(ts != 0, "FFMPEG: negative start_ts (%li)\n", (long)ts);
|
||||
if (ts != 0)
|
||||
ts = 0;
|
||||
|
||||
/* add index 0 */
|
||||
ret = av_add_index_entry(stream, pos, ts, size, distance, AVINDEX_KEYFRAME);
|
||||
if ( ret < 0 )
|
||||
return ret;
|
||||
|
||||
|
||||
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 */
|
||||
|
||||
avcodec_flush_buffers(data->codecCtx);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* ******************************************** */
|
||||
/* AVIO CALLBACKS */
|
||||
/* ******************************************** */
|
||||
|
||||
/* AVIO callback: read stream, skipping external headers if needed */
|
||||
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 = 0;
|
||||
int ret;
|
||||
|
||||
if (data->header_insert_block) {
|
||||
if (offset < data->header_size) {
|
||||
max_to_copy = (int)(data->header_size - offset);
|
||||
if (max_to_copy > buf_size) {
|
||||
max_to_copy = buf_size;
|
||||
}
|
||||
memcpy(buf, data->header_insert_block + offset, max_to_copy);
|
||||
buf += max_to_copy;
|
||||
buf_size -= max_to_copy;
|
||||
offset += max_to_copy;
|
||||
if (!buf_size) {
|
||||
data->offset = offset;
|
||||
return max_to_copy;
|
||||
}
|
||||
}
|
||||
offset -= data->header_size;
|
||||
}
|
||||
|
||||
/* when "fake" size is smaller than "real" size we need to make sure bytes_read (ret) is clamped;
|
||||
* it confuses FFmpeg in rare cases (STREAMFILE may have valid data after size) */
|
||||
if (offset + buf_size > data->size + data->header_size) {
|
||||
buf_size = data->size - offset; /* header "read" is manually inserted later */
|
||||
}
|
||||
|
||||
ret = read_streamfile(buf, offset + data->start, buf_size, data->streamfile);
|
||||
if (ret > 0) {
|
||||
offset += ret;
|
||||
if (data->header_insert_block) {
|
||||
ret += max_to_copy;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->header_insert_block) {
|
||||
offset += data->header_size;
|
||||
}
|
||||
|
||||
data->offset = offset;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* AVIO callback: write stream not needed */
|
||||
static int ffmpeg_write(void *opaque, uint8_t *buf, int buf_size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* AVIO callback: seek stream, skipping external headers if needed */
|
||||
static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) {
|
||||
ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque;
|
||||
int ret = 0;
|
||||
|
||||
if (whence & AVSEEK_SIZE) {
|
||||
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;
|
||||
|
||||
case SEEK_END:
|
||||
offset += data->size;
|
||||
if (data->header_insert_block)
|
||||
offset += data->header_size;
|
||||
break;
|
||||
}
|
||||
|
||||
/* clamp offset; fseek returns 0 when offset > size, too */
|
||||
if (offset > data->size + data->header_size) {
|
||||
offset = data->size + data->header_size;
|
||||
}
|
||||
|
||||
data->offset = offset;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* ******************************************** */
|
||||
/* MAIN INIT/DECODER */
|
||||
/* ******************************************** */
|
||||
|
||||
/**
|
||||
* Manually init FFmpeg, from an offset.
|
||||
* Used if the stream has internal data recognized by FFmpeg.
|
||||
*/
|
||||
ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
|
||||
return init_ffmpeg_header_offset(streamFile, NULL, 0, start, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually init FFmpeg, from a fake header / offset.
|
||||
*
|
||||
* Takes a fake header, to trick FFmpeg into demuxing/decoding the stream.
|
||||
* This header will be seamlessly inserted before 'start' offset, and total filesize will be 'header_size' + 'size'.
|
||||
* The header buffer will be copied and memory-managed internally.
|
||||
*/
|
||||
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size) {
|
||||
char filename[PATH_LIMIT];
|
||||
ffmpeg_codec_data * data;
|
||||
int errcode, i;
|
||||
|
||||
int streamIndex, streamCount;
|
||||
AVStream *stream;
|
||||
AVCodecParameters *codecPar;
|
||||
|
||||
AVRational tb;
|
||||
|
||||
|
||||
/* basic setup */
|
||||
g_init_ffmpeg();
|
||||
|
||||
data = ( ffmpeg_codec_data * ) calloc(1, sizeof(ffmpeg_codec_data));
|
||||
if (!data) return NULL;
|
||||
|
||||
streamFile->get_name( streamFile, filename, sizeof(filename) );
|
||||
|
||||
data->streamfile = streamFile->open(streamFile, filename, STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!data->streamfile) goto fail;
|
||||
|
||||
data->start = start;
|
||||
data->size = size;
|
||||
|
||||
|
||||
/* insert fake header to trick FFmpeg into demuxing/decoding the stream */
|
||||
if (header_size > 0) {
|
||||
data->header_size = header_size;
|
||||
data->header_insert_block = av_memdup(header, header_size);
|
||||
if (!data->header_insert_block) goto fail;
|
||||
}
|
||||
|
||||
/* setup IO, attempt to autodetect format and gather some info */
|
||||
data->buffer = av_malloc(FFMPEG_DEFAULT_IO_BUFFER_SIZE);
|
||||
if (!data->buffer) goto fail;
|
||||
|
||||
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();
|
||||
if (!data->formatCtx) goto fail;
|
||||
|
||||
data->formatCtx->pb = data->ioCtx;
|
||||
|
||||
if ((errcode = avformat_open_input(&data->formatCtx, "", NULL, NULL)) < 0) goto fail; /* autodetect */
|
||||
|
||||
if ((errcode = avformat_find_stream_info(data->formatCtx, NULL)) < 0) goto fail;
|
||||
|
||||
|
||||
/* find valid audio stream inside */
|
||||
streamIndex = -1;
|
||||
streamCount = 0; /* audio streams only */
|
||||
|
||||
for (i = 0; i < data->formatCtx->nb_streams; ++i) {
|
||||
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 (codecPar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
streamCount++;
|
||||
}
|
||||
|
||||
if (streamIndex < 0) goto fail;
|
||||
|
||||
data->streamIndex = streamIndex;
|
||||
stream = data->formatCtx->streams[streamIndex];
|
||||
data->streamCount = streamCount;
|
||||
|
||||
|
||||
/* prepare codec and frame/packet buffers */
|
||||
data->codecCtx = avcodec_alloc_context3(NULL);
|
||||
if (!data->codecCtx) goto fail;
|
||||
|
||||
if ((errcode = avcodec_parameters_to_context(data->codecCtx, codecPar)) < 0) goto fail;
|
||||
|
||||
av_codec_set_pkt_timebase(data->codecCtx, stream->time_base);
|
||||
|
||||
data->codec = avcodec_find_decoder(data->codecCtx->codec_id);
|
||||
if (!data->codec) goto fail;
|
||||
|
||||
if ((errcode = avcodec_open2(data->codecCtx, data->codec, NULL)) < 0) goto fail;
|
||||
|
||||
data->lastDecodedFrame = av_frame_alloc();
|
||||
if (!data->lastDecodedFrame) goto fail;
|
||||
av_frame_unref(data->lastDecodedFrame);
|
||||
|
||||
data->lastReadPacket = malloc(sizeof(AVPacket));
|
||||
if (!data->lastReadPacket) goto fail;
|
||||
av_new_packet(data->lastReadPacket, 0);
|
||||
|
||||
data->readNextPacket = 1;
|
||||
data->bytesConsumedFromDecodedFrame = INT_MAX;
|
||||
|
||||
|
||||
/* other setup */
|
||||
data->sampleRate = data->codecCtx->sample_rate;
|
||||
data->channels = data->codecCtx->channels;
|
||||
data->floatingPoint = 0;
|
||||
|
||||
switch (data->codecCtx->sample_fmt) {
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
case AV_SAMPLE_FMT_U8P:
|
||||
data->bitsPerSample = 8;
|
||||
break;
|
||||
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S16P:
|
||||
data->bitsPerSample = 16;
|
||||
break;
|
||||
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
case AV_SAMPLE_FMT_S32P:
|
||||
data->bitsPerSample = 32;
|
||||
break;
|
||||
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
case AV_SAMPLE_FMT_FLTP:
|
||||
data->bitsPerSample = 32;
|
||||
data->floatingPoint = 1;
|
||||
break;
|
||||
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
case AV_SAMPLE_FMT_DBLP:
|
||||
data->bitsPerSample = 64;
|
||||
data->floatingPoint = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->bitrate = (int)(data->codecCtx->bit_rate);
|
||||
data->endOfStream = 0;
|
||||
data->endOfAudio = 0;
|
||||
|
||||
/* try to guess frames/samples (duration isn't always set) */
|
||||
tb.num = 1; tb.den = data->codecCtx->sample_rate;
|
||||
data->totalSamples = av_rescale_q(stream->duration, stream->time_base, tb);
|
||||
if (data->totalSamples < 0)
|
||||
data->totalSamples = 0; /* caller must consider this */
|
||||
|
||||
data->blockAlign = data->codecCtx->block_align;
|
||||
data->frameSize = data->codecCtx->frame_size;
|
||||
if(data->frameSize == 0) /* some formats don't set frame_size but can get on request, and vice versa */
|
||||
data->frameSize = av_get_audio_frame_duration(data->codecCtx,0);
|
||||
|
||||
/* setup decode buffer */
|
||||
data->sampleBufferBlock = FFMPEG_DEFAULT_BUFFER_SIZE;
|
||||
data->sampleBuffer = av_malloc( data->sampleBufferBlock * (data->bitsPerSample / 8) * data->channels );
|
||||
if (!data->sampleBuffer)
|
||||
goto fail;
|
||||
|
||||
|
||||
/* setup decent seeking for faulty formats */
|
||||
errcode = init_seek(data);
|
||||
if (errcode < 0) goto fail;
|
||||
|
||||
/* expose start samples to be skipped (encoder delay, usually added by MDCT-based encoders like AAC/MP3/ATRAC3/XMA/etc)
|
||||
* get after init_seek because some demuxers like AAC only fill skip_samples for the first packet */
|
||||
if (stream->start_skip_samples) /* samples to skip in the first packet */
|
||||
data->skipSamples = stream->start_skip_samples;
|
||||
else if (stream->skip_samples) /* samples to skip in any packet (first in this case), used sometimes instead (ex. AAC) */
|
||||
data->skipSamples = stream->skip_samples;
|
||||
|
||||
return data;
|
||||
|
||||
fail:
|
||||
free_ffmpeg(data);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* decode samples of any kind of FFmpeg format */
|
||||
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 bytesToRead;
|
||||
int bytesRead;
|
||||
int bytesPerSample, bytesPerFrame, frameSize;
|
||||
int bytesToRead, bytesRead;
|
||||
|
||||
uint8_t *targetBuf;
|
||||
|
||||
@ -78,10 +480,7 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do,
|
||||
|
||||
int bytesConsumedFromDecodedFrame;
|
||||
|
||||
int readNextPacket;
|
||||
int endOfStream;
|
||||
int endOfAudio;
|
||||
|
||||
int readNextPacket, endOfStream, endOfAudio;
|
||||
int framesReadNow;
|
||||
|
||||
|
||||
@ -114,12 +513,7 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do,
|
||||
|
||||
/* keep reading and decoding packets until the requested number of samples (in bytes) */
|
||||
while (bytesRead < bytesToRead) {
|
||||
int planeSize;
|
||||
int planar;
|
||||
int dataSize;
|
||||
int toConsume;
|
||||
int errcode;
|
||||
|
||||
int planeSize, planar, dataSize, toConsume, errcode;
|
||||
|
||||
/* size of previous frame */
|
||||
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, lastDecodedFrame->nb_samples, codecCtx->sample_fmt, 1);
|
||||
@ -231,10 +625,10 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do,
|
||||
end:
|
||||
framesReadNow = bytesRead / frameSize;
|
||||
|
||||
// Convert the audio
|
||||
/* Convert the audio */
|
||||
convert_audio(outbuf, data->sampleBuffer, framesReadNow * channels, data->bitsPerSample, data->floatingPoint);
|
||||
|
||||
// Output the state back to the structure
|
||||
/* Output the state back to the structure */
|
||||
data->bytesConsumedFromDecodedFrame = bytesConsumedFromDecodedFrame;
|
||||
data->readNextPacket = readNextPacket;
|
||||
data->endOfStream = endOfStream;
|
||||
@ -242,6 +636,10 @@ end:
|
||||
}
|
||||
|
||||
|
||||
/* ******************************************** */
|
||||
/* UTILS */
|
||||
/* ******************************************** */
|
||||
|
||||
void reset_ffmpeg(VGMSTREAM *vgmstream) {
|
||||
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
|
||||
|
||||
@ -268,7 +666,6 @@ void reset_ffmpeg(VGMSTREAM *vgmstream) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) {
|
||||
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
|
||||
int64_t ts;
|
||||
@ -297,6 +694,55 @@ void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) {
|
||||
}
|
||||
}
|
||||
|
||||
void free_ffmpeg(ffmpeg_codec_data *data) {
|
||||
if (data == NULL)
|
||||
return;
|
||||
|
||||
if (data->lastReadPacket) {
|
||||
av_packet_unref(data->lastReadPacket);
|
||||
free(data->lastReadPacket);
|
||||
data->lastReadPacket = NULL;
|
||||
}
|
||||
if (data->lastDecodedFrame) {
|
||||
av_free(data->lastDecodedFrame);
|
||||
data->lastDecodedFrame = NULL;
|
||||
}
|
||||
if (data->codecCtx) {
|
||||
avcodec_close(data->codecCtx);
|
||||
avcodec_free_context(&(data->codecCtx));
|
||||
data->codecCtx = NULL;
|
||||
}
|
||||
if (data->formatCtx) {
|
||||
avformat_close_input(&(data->formatCtx));
|
||||
data->formatCtx = NULL;
|
||||
}
|
||||
if (data->ioCtx) {
|
||||
// buffer passed in is occasionally freed and replaced.
|
||||
// the replacement must be freed as well.
|
||||
data->buffer = data->ioCtx->buffer;
|
||||
av_free(data->ioCtx);
|
||||
data->ioCtx = NULL;
|
||||
}
|
||||
if (data->buffer) {
|
||||
av_free(data->buffer);
|
||||
data->buffer = NULL;
|
||||
}
|
||||
if (data->sampleBuffer) {
|
||||
av_free(data->sampleBuffer);
|
||||
data->sampleBuffer = NULL;
|
||||
}
|
||||
if (data->header_insert_block) {
|
||||
av_free(data->header_insert_block);
|
||||
data->header_insert_block = NULL;
|
||||
}
|
||||
if (data->streamfile) {
|
||||
close_streamfile(data->streamfile);
|
||||
data->streamfile = NULL;
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the number of samples to skip at the beginning of the stream, needed by some "gapless" formats.
|
||||
* (encoder delay, usually added by MDCT-based encoders like AAC/MP3/ATRAC3/XMA/etc to "set up" the decoder).
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
|
||||
/* AKB (AAC only) - found in SQEX iOS games */
|
||||
|
@ -1,41 +1,11 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
/* internal sizes, can be any value */
|
||||
#define FFMPEG_DEFAULT_BUFFER_SIZE 2048
|
||||
#define FFMPEG_DEFAULT_IO_BUFFER_SIZE 128 * 1024
|
||||
|
||||
static int init_seek(ffmpeg_codec_data * data);
|
||||
|
||||
|
||||
static volatile int g_ffmpeg_initialized = 0;
|
||||
|
||||
/*
|
||||
* Global FFmpeg init
|
||||
*/
|
||||
static void g_init_ffmpeg()
|
||||
{
|
||||
if (g_ffmpeg_initialized == 1)
|
||||
{
|
||||
while (g_ffmpeg_initialized < 2);
|
||||
}
|
||||
else if (g_ffmpeg_initialized == 0)
|
||||
{
|
||||
g_ffmpeg_initialized = 1;
|
||||
av_log_set_flags(AV_LOG_SKIP_REPEATED);
|
||||
av_log_set_level(AV_LOG_ERROR);
|
||||
av_register_all();
|
||||
g_ffmpeg_initialized = 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic init FFmpeg and vgmstream for any file supported by FFmpeg.
|
||||
* Always called by vgmstream when trying to identify the file type (if the player allows it).
|
||||
* Called by vgmstream when trying to identify the file type (if the player allows it).
|
||||
*/
|
||||
VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile) {
|
||||
return init_vgmstream_ffmpeg_offset( streamFile, 0, streamFile->get_size(streamFile) );
|
||||
@ -105,436 +75,4 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AVIO callback: read stream, skipping external headers if needed
|
||||
*/
|
||||
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 = 0;
|
||||
int ret;
|
||||
|
||||
if (data->header_insert_block) {
|
||||
if (offset < data->header_size) {
|
||||
max_to_copy = (int)(data->header_size - offset);
|
||||
if (max_to_copy > buf_size) {
|
||||
max_to_copy = buf_size;
|
||||
}
|
||||
memcpy(buf, data->header_insert_block + offset, max_to_copy);
|
||||
buf += max_to_copy;
|
||||
buf_size -= max_to_copy;
|
||||
offset += max_to_copy;
|
||||
if (!buf_size) {
|
||||
data->offset = offset;
|
||||
return max_to_copy;
|
||||
}
|
||||
}
|
||||
offset -= data->header_size;
|
||||
}
|
||||
|
||||
/* when "fake" size is smaller than "real" size we need to make sure bytes_read (ret) is clamped;
|
||||
* it confuses FFmpeg in rare cases (STREAMFILE may have valid data after size) */
|
||||
if (offset + buf_size > data->size + data->header_size) {
|
||||
buf_size = data->size - offset; /* header "read" is manually inserted later */
|
||||
}
|
||||
|
||||
ret = read_streamfile(buf, offset + data->start, buf_size, data->streamfile);
|
||||
if (ret > 0) {
|
||||
offset += ret;
|
||||
if (data->header_insert_block) {
|
||||
ret += max_to_copy;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->header_insert_block) {
|
||||
offset += data->header_size;
|
||||
}
|
||||
|
||||
data->offset = offset;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* AVIO callback: write stream not needed
|
||||
*/
|
||||
static int ffmpeg_write(void *opaque, uint8_t *buf, int buf_size)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* AVIO callback: seek stream, skipping external headers if needed
|
||||
*/
|
||||
static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence)
|
||||
{
|
||||
ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque;
|
||||
int ret = 0;
|
||||
|
||||
if (whence & AVSEEK_SIZE) {
|
||||
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;
|
||||
|
||||
case SEEK_END:
|
||||
offset += data->size;
|
||||
if (data->header_insert_block)
|
||||
offset += data->header_size;
|
||||
break;
|
||||
}
|
||||
|
||||
/* clamp offset; fseek returns 0 when offset > size, too */
|
||||
if (offset > data->size + data->header_size) {
|
||||
offset = data->size + data->header_size;
|
||||
}
|
||||
|
||||
data->offset = offset;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Manually init FFmpeg, from an offset.
|
||||
* Can be used if the stream has an extra header over data recognized by FFmpeg.
|
||||
*/
|
||||
ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
|
||||
return init_ffmpeg_header_offset(streamFile, NULL, 0, start, size);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Manually init FFmpeg, from a fake header / offset.
|
||||
*
|
||||
* Can take a fake header, to trick FFmpeg into demuxing/decoding the stream.
|
||||
* This header will be seamlessly inserted before 'start' offset, and total filesize will be 'header_size' + 'size'.
|
||||
* The header buffer will be copied and memory-managed internally.
|
||||
*/
|
||||
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size) {
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
ffmpeg_codec_data * data;
|
||||
|
||||
int errcode, i;
|
||||
|
||||
int streamIndex, streamCount;
|
||||
AVStream *stream;
|
||||
AVCodecParameters *codecPar;
|
||||
|
||||
AVRational tb;
|
||||
|
||||
|
||||
/* basic setup */
|
||||
g_init_ffmpeg();
|
||||
|
||||
data = ( ffmpeg_codec_data * ) calloc(1, sizeof(ffmpeg_codec_data));
|
||||
if (!data) return NULL;
|
||||
|
||||
streamFile->get_name( streamFile, filename, sizeof(filename) );
|
||||
|
||||
data->streamfile = streamFile->open(streamFile, filename, STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!data->streamfile) goto fail;
|
||||
|
||||
data->start = start;
|
||||
data->size = size;
|
||||
|
||||
|
||||
/* insert fake header to trick FFmpeg into demuxing/decoding the stream */
|
||||
if (header_size > 0) {
|
||||
data->header_size = header_size;
|
||||
data->header_insert_block = av_memdup(header, header_size);
|
||||
if (!data->header_insert_block) goto fail;
|
||||
}
|
||||
|
||||
/* setup IO, attempt to autodetect format and gather some info */
|
||||
data->buffer = av_malloc(FFMPEG_DEFAULT_IO_BUFFER_SIZE);
|
||||
if (!data->buffer) goto fail;
|
||||
|
||||
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();
|
||||
if (!data->formatCtx) goto fail;
|
||||
|
||||
data->formatCtx->pb = data->ioCtx;
|
||||
|
||||
if ((errcode = avformat_open_input(&data->formatCtx, "", NULL, NULL)) < 0) goto fail; /* autodetect */
|
||||
|
||||
if ((errcode = avformat_find_stream_info(data->formatCtx, NULL)) < 0) goto fail;
|
||||
|
||||
|
||||
/* find valid audio stream inside */
|
||||
streamIndex = -1;
|
||||
streamCount = 0; /* audio streams only */
|
||||
|
||||
for (i = 0; i < data->formatCtx->nb_streams; ++i) {
|
||||
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 (codecPar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
streamCount++;
|
||||
}
|
||||
|
||||
if (streamIndex < 0) goto fail;
|
||||
|
||||
data->streamIndex = streamIndex;
|
||||
stream = data->formatCtx->streams[streamIndex];
|
||||
data->streamCount = streamCount;
|
||||
|
||||
|
||||
/* prepare codec and frame/packet buffers */
|
||||
data->codecCtx = avcodec_alloc_context3(NULL);
|
||||
if (!data->codecCtx) goto fail;
|
||||
|
||||
if ((errcode = avcodec_parameters_to_context(data->codecCtx, codecPar)) < 0) goto fail;
|
||||
|
||||
av_codec_set_pkt_timebase(data->codecCtx, stream->time_base);
|
||||
|
||||
data->codec = avcodec_find_decoder(data->codecCtx->codec_id);
|
||||
if (!data->codec) goto fail;
|
||||
|
||||
if ((errcode = avcodec_open2(data->codecCtx, data->codec, NULL)) < 0) goto fail;
|
||||
|
||||
data->lastDecodedFrame = av_frame_alloc();
|
||||
if (!data->lastDecodedFrame) goto fail;
|
||||
av_frame_unref(data->lastDecodedFrame);
|
||||
|
||||
data->lastReadPacket = malloc(sizeof(AVPacket));
|
||||
if (!data->lastReadPacket) goto fail;
|
||||
av_new_packet(data->lastReadPacket, 0);
|
||||
|
||||
data->readNextPacket = 1;
|
||||
data->bytesConsumedFromDecodedFrame = INT_MAX;
|
||||
|
||||
|
||||
/* other setup */
|
||||
data->sampleRate = data->codecCtx->sample_rate;
|
||||
data->channels = data->codecCtx->channels;
|
||||
data->floatingPoint = 0;
|
||||
|
||||
switch (data->codecCtx->sample_fmt) {
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
case AV_SAMPLE_FMT_U8P:
|
||||
data->bitsPerSample = 8;
|
||||
break;
|
||||
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S16P:
|
||||
data->bitsPerSample = 16;
|
||||
break;
|
||||
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
case AV_SAMPLE_FMT_S32P:
|
||||
data->bitsPerSample = 32;
|
||||
break;
|
||||
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
case AV_SAMPLE_FMT_FLTP:
|
||||
data->bitsPerSample = 32;
|
||||
data->floatingPoint = 1;
|
||||
break;
|
||||
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
case AV_SAMPLE_FMT_DBLP:
|
||||
data->bitsPerSample = 64;
|
||||
data->floatingPoint = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->bitrate = (int)(data->codecCtx->bit_rate);
|
||||
data->endOfStream = 0;
|
||||
data->endOfAudio = 0;
|
||||
|
||||
/* try to guess frames/samples (duration isn't always set) */
|
||||
tb.num = 1; tb.den = data->codecCtx->sample_rate;
|
||||
data->totalSamples = av_rescale_q(stream->duration, stream->time_base, tb);
|
||||
if (data->totalSamples < 0)
|
||||
data->totalSamples = 0; /* caller must consider this */
|
||||
|
||||
data->blockAlign = data->codecCtx->block_align;
|
||||
data->frameSize = data->codecCtx->frame_size;
|
||||
if(data->frameSize == 0) /* some formats don't set frame_size but can get on request, and vice versa */
|
||||
data->frameSize = av_get_audio_frame_duration(data->codecCtx,0);
|
||||
|
||||
/* setup decode buffer */
|
||||
data->sampleBufferBlock = FFMPEG_DEFAULT_BUFFER_SIZE;
|
||||
data->sampleBuffer = av_malloc( data->sampleBufferBlock * (data->bitsPerSample / 8) * data->channels );
|
||||
if (!data->sampleBuffer)
|
||||
goto fail;
|
||||
|
||||
|
||||
/* setup decent seeking for faulty formats */
|
||||
errcode = init_seek(data);
|
||||
if (errcode < 0) goto fail;
|
||||
|
||||
/* expose start samples to be skipped (encoder delay, usually added by MDCT-based encoders like AAC/MP3/ATRAC3/XMA/etc)
|
||||
* get after init_seek because some demuxers like AAC only fill skip_samples for the first packet */
|
||||
if (stream->start_skip_samples) /* samples to skip in the first packet */
|
||||
data->skipSamples = stream->start_skip_samples;
|
||||
else if (stream->skip_samples) /* samples to skip in any packet (first in this case), used sometimes instead (ex. AAC) */
|
||||
data->skipSamples = stream->skip_samples;
|
||||
|
||||
return data;
|
||||
|
||||
fail:
|
||||
free_ffmpeg(data);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Some formats may not seek to 0 even with this, though.
|
||||
*/
|
||||
static int init_seek(ffmpeg_codec_data * data) {
|
||||
int ret, ts_index, found_first = 0;
|
||||
int64_t ts = 0;
|
||||
int64_t pos = 0; /* offset */
|
||||
int size = 0; /* coded size */
|
||||
int distance = 0; /* always? */
|
||||
|
||||
AVStream * stream;
|
||||
AVPacket * pkt;
|
||||
|
||||
stream = data->formatCtx->streams[data->streamIndex];
|
||||
pkt = data->lastReadPacket;
|
||||
|
||||
/* 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;
|
||||
if (pkt->stream_index != data->streamIndex)
|
||||
continue; /* ignore non-selected streams */
|
||||
|
||||
if (!found_first) { /* first found */
|
||||
found_first = 1;
|
||||
pos = pkt->pos;
|
||||
ts = pkt->dts;
|
||||
continue;
|
||||
} else { /* second found */
|
||||
size = pkt->pos - pos; /* coded, pkt->size is decoded size */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_first)
|
||||
goto fail;
|
||||
|
||||
/* in rare cases there is only one packet */
|
||||
/* if (size == 0) { size = data_end - pos; } */ /* no easy way to know, ignore (most formats don's need size) */
|
||||
|
||||
/* some formats (XMA1) don't seem to have packet.dts, pretend it's 0 */
|
||||
if (ts == INT64_MIN)
|
||||
ts = 0;
|
||||
|
||||
/* Some streams start with negative DTS (observed in Ogg). For Ogg seeking to negative or 0 doesn't alter the output.
|
||||
* It does seem seeking before decoding alters a bunch of (inaudible) +-1 lower bytes though. */
|
||||
VGM_ASSERT(ts != 0, "FFMPEG: negative start_ts (%li)\n", (long)ts);
|
||||
if (ts != 0)
|
||||
ts = 0;
|
||||
|
||||
/* add index 0 */
|
||||
ret = av_add_index_entry(stream, pos, ts, size, distance, AVINDEX_KEYFRAME);
|
||||
if ( ret < 0 )
|
||||
return ret;
|
||||
|
||||
|
||||
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 */
|
||||
|
||||
avcodec_flush_buffers(data->codecCtx);
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void free_ffmpeg(ffmpeg_codec_data *data) {
|
||||
if (data == NULL)
|
||||
return;
|
||||
|
||||
if (data->lastReadPacket) {
|
||||
av_packet_unref(data->lastReadPacket);
|
||||
free(data->lastReadPacket);
|
||||
data->lastReadPacket = NULL;
|
||||
}
|
||||
if (data->lastDecodedFrame) {
|
||||
av_free(data->lastDecodedFrame);
|
||||
data->lastDecodedFrame = NULL;
|
||||
}
|
||||
if (data->codecCtx) {
|
||||
avcodec_close(data->codecCtx);
|
||||
avcodec_free_context(&(data->codecCtx));
|
||||
data->codecCtx = NULL;
|
||||
}
|
||||
if (data->formatCtx) {
|
||||
avformat_close_input(&(data->formatCtx));
|
||||
data->formatCtx = NULL;
|
||||
}
|
||||
if (data->ioCtx) {
|
||||
// buffer passed in is occasionally freed and replaced.
|
||||
// the replacement must be freed as well.
|
||||
data->buffer = data->ioCtx->buffer;
|
||||
av_free(data->ioCtx);
|
||||
data->ioCtx = NULL;
|
||||
}
|
||||
if (data->buffer) {
|
||||
av_free(data->buffer);
|
||||
data->buffer = NULL;
|
||||
}
|
||||
if (data->sampleBuffer) {
|
||||
av_free(data->sampleBuffer);
|
||||
data->sampleBuffer = NULL;
|
||||
}
|
||||
if (data->header_insert_block) {
|
||||
av_free(data->header_insert_block);
|
||||
data->header_insert_block = NULL;
|
||||
}
|
||||
if (data->streamfile) {
|
||||
close_streamfile(data->streamfile);
|
||||
data->streamfile = NULL;
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
#endif
|
||||
|
@ -121,21 +121,14 @@ VGMSTREAM * init_vgmstream_sli_ogg(STREAMFILE * streamFile);
|
||||
VGMSTREAM * init_vgmstream_hca(STREAMFILE *streamFile);
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
|
||||
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size);
|
||||
|
||||
void free_ffmpeg(ffmpeg_codec_data *data);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
|
||||
|
||||
VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE * streamFile);
|
||||
#endif
|
||||
|
||||
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
|
||||
VGMSTREAM * init_vgmstream_mp4_aac(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
|
||||
|
||||
VGMSTREAM * init_vgmstream_akb(STREAMFILE *streamFile);
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
#ifdef VGM_USE_MP4V2
|
||||
void* mp4_file_open( const char* name, MP4FileMode mode )
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* utils to fix AT3 looping */
|
||||
|
Loading…
Reference in New Issue
Block a user