ffmpeg second api

This commit is contained in:
2026-04-26 11:49:42 +02:00
parent 38bc2445e1
commit 1a8646fd8b
3 changed files with 694 additions and 525 deletions
+88 -63
View File
@@ -3,7 +3,6 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#ifdef WIN32 #ifdef WIN32
#define fprintf fprintf_s #define fprintf fprintf_s
@@ -26,18 +25,18 @@ static int write_wav_header(FILE *f,
int channels, int channels,
int bits_per_sample, int bits_per_sample,
uint32_t data_size) { uint32_t data_size) {
uint32_t byte_rate = const uint32_t byte_rate =
(uint32_t)(sample_rate * channels * bits_per_sample / 8); (uint32_t)(sample_rate * channels * bits_per_sample / 8);
uint16_t block_align = const uint16_t block_align =
(uint16_t)(channels * bits_per_sample / 8); (uint16_t)(channels * bits_per_sample / 8);
fwrite("RIFF", 1, 4, f); fwrite("RIFF", 1, 4, f);
write_u32_le(f, 36 + data_size); write_u32_le(f, 36u + data_size);
fwrite("WAVE", 1, 4, f); fwrite("WAVE", 1, 4, f);
fwrite("fmt ", 1, 4, f); fwrite("fmt ", 1, 4, f);
write_u32_le(f, 16); /* fmt chunk size */ write_u32_le(f, 16); /* fmt chunk size */
write_u16_le(f, 1); /* PCM */ write_u16_le(f, 1); /* 1 = integer PCM */
write_u16_le(f, (uint16_t)channels); write_u16_le(f, (uint16_t)channels);
write_u32_le(f, (uint32_t)sample_rate); write_u32_le(f, (uint32_t)sample_rate);
write_u32_le(f, byte_rate); write_u32_le(f, byte_rate);
@@ -66,16 +65,37 @@ static int rewrite_wav_header(FILE *f,
data_size); data_size);
} }
static int write_decoder_buffer(FILE *out,
fmpg_decoder *dec,
uint64_t *total_written) {
const uint8_t *buf = fmpg_decoder_buffer(dec);
const int size = fmpg_decoder_buffer_size(dec);
if (!buf || size <= 0) {
return 1;
}
if (fwrite(buf, 1, (size_t)size, out) != (size_t)size) {
return 0;
}
*total_written += (uint64_t)size;
return 1;
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
const char *infile; const char *infile;
const char *outfile; const char *outfile;
fmpg_instance * ac = NULL; fmpg_instance *ac = NULL;
fmpg_decoder * dec = NULL; fmpg_decoder *dec = NULL;
FILE *out = NULL; FILE *out = NULL;
int stream; int sample_rate;
fmpg_audio_info info; int channels;
int bits_per_sample;
int64_t duration_ms;
int64_t duration_samples;
uint64_t total_written = 0; uint64_t total_written = 0;
if (argc != 3) { if (argc != 3) {
@@ -86,44 +106,42 @@ int main(int argc, char **argv) {
infile = argv[1]; infile = argv[1];
outfile = argv[2]; outfile = argv[2];
ac = ac_init(); ac = fmpg_init();
if (!ac) { if (!ac) {
fprintf(stderr, "ac_init failed\n"); fprintf(stderr, "ac_init failed\n");
return 1; return 1;
} }
if (!ac_open_file(ac, infile)) { if (!fmpg_open_file(ac, infile)) {
fprintf(stderr, "could not open input file: %s\n", infile); fprintf(stderr, "could not open input file: %s\n", infile);
ac_free(ac); fmpg_free(ac);
return 1; return 1;
} }
stream = ac_get_default_audio_stream(ac); sample_rate = fmpg_audio_sample_rate(ac);
if (stream < 0) { channels = fmpg_audio_channels(ac);
fprintf(stderr, "no audio stream found\n"); bits_per_sample = fmpg_audio_bits_per_sample(ac);
ac_free(ac); duration_ms = fmpg_duration_ms(ac);
duration_samples = fmpg_duration_samples(ac);
if (sample_rate <= 0 || channels <= 0 || bits_per_sample != 32) {
fprintf(stderr, "invalid audio parameters\n");
fmpg_free(ac);
return 1; return 1;
} }
memset(&info, 0, sizeof(info)); dec = fmpg_create_decoder(ac);
if (!ac_get_audio_info(ac, stream, &info)) {
fprintf(stderr, "could not get audio info\n");
ac_free(ac);
return 1;
}
dec = ac_create_decoder(ac, stream);
if (!dec) { if (!dec) {
fprintf(stderr, "could not create decoder\n"); fprintf(stderr, "could not create decoder\n");
ac_free(ac); fmpg_free(ac);
return 1; return 1;
} }
out = fopen(outfile, "wb"); out = fopen(outfile, "wb");
if (!out) { if (!out) {
fprintf(stderr, "could not open output file: %s\n", outfile); fprintf(stderr, "could not open output file: %s\n", outfile);
ac_free_decoder(dec); fmpg_free_decoder(dec);
ac_free(ac); fmpg_free(ac);
return 1; return 1;
} }
@@ -132,49 +150,50 @@ int main(int argc, char **argv) {
* Write a placeholder header first and patch it at the end. * Write a placeholder header first and patch it at the end.
*/ */
if (!write_wav_header(out, if (!write_wav_header(out,
info.sample_rate, sample_rate,
info.channels, channels,
32, bits_per_sample,
0)) { 0)) {
fprintf(stderr, "could not write WAV header\n"); fprintf(stderr, "could not write WAV header\n");
fclose(out); fclose(out);
ac_free_decoder(dec); fmpg_free_decoder(dec);
ac_free(ac); fmpg_free(ac);
return 1; return 1;
} }
for (;;) { for (;;) {
fmpg_package * pkg = ac_read_package(ac); fmpg_package *pkg = fmpg_read_package(ac);
if (!pkg) { if (!pkg) {
break; break;
} }
if (ac_package_stream_index(pkg) == stream) {
if (ac_decode_package(pkg, dec)) {
const uint8_t *buf = ac_decoder_buffer(dec);
int size = ac_decoder_buffer_size(dec);
if (buf && size > 0) {
fwrite(buf, 1, (size_t)size, out);
total_written += (uint64_t)size;
}
}
}
ac_free_package(pkg);
}
/* /*
* Drain delayed samples from the decoder and resampler. * ac_read_package() now returns only packets from the internally
* selected audio stream. No stream_index test is needed anymore.
*/ */
while (ac_flush_decoder(dec)) { if (fmpg_decode_package(pkg, dec)) {
const uint8_t *buf = ac_decoder_buffer(dec); if (!write_decoder_buffer(out, dec, &total_written)) {
int size = ac_decoder_buffer_size(dec); fprintf(stderr, "could not write PCM data\n");
fmpg_free_package(pkg);
fclose(out);
fmpg_free_decoder(dec);
fmpg_free(ac);
return 1;
}
}
if (buf && size > 0) { fmpg_free_package(pkg);
fwrite(buf, 1, (size_t)size, out); }
total_written += (uint64_t)size;
/* Drain delayed samples from the decoder and resampler. */
while (fmpg_flush_decoder(dec)) {
if (!write_decoder_buffer(out, dec, &total_written)) {
fprintf(stderr, "could not write flushed PCM data\n");
fclose(out);
fmpg_free_decoder(dec);
fmpg_free(ac);
return 1;
} }
} }
@@ -185,23 +204,29 @@ int main(int argc, char **argv) {
} }
if (!rewrite_wav_header(out, if (!rewrite_wav_header(out,
info.sample_rate, sample_rate,
info.channels, channels,
32, bits_per_sample,
(uint32_t)total_written)) { (uint32_t)total_written)) {
fprintf(stderr, "could not rewrite WAV header\n"); fprintf(stderr, "could not rewrite WAV header\n");
} }
fclose(out); fclose(out);
ac_free_decoder(dec);
ac_free(ac);
printf("wrote %s\n", outfile); printf("wrote %s\n", outfile);
printf("sample rate: %d\n", info.sample_rate); printf("title: %s\n", fmpg_file_title(ac));
printf("channels: %d\n", info.channels); printf("album: %s\n", fmpg_file_album(ac));
printf("sample bits: %d\n", 32); printf("sample rate: %d\n", sample_rate);
printf("channels: %d\n", channels);
printf("sample bits: %d\n", bits_per_sample);
printf("duration ms: %lld\n", (long long)duration_ms);
printf("duration smp:%lld\n", (long long)duration_samples);
printf("decoded smp: %lld\n", (long long)fmpg_decoder_sample_position(dec));
printf("data bytes: %llu\n", printf("data bytes: %llu\n",
(unsigned long long)total_written); (unsigned long long)total_written);
fmpg_free_decoder(dec);
fmpg_free(ac);
return 0; return 0;
} }
+301 -309
View File
@@ -1,43 +1,18 @@
/* /*
* Acinerella audio-only decoder. * Audio-only FFmpeg wrapper.
* *
* This file is intentionally written as C++ internally, but exports a stable * This file is implemented in C++, but exports a plain C ABI. C++ is used only
* C ABI. That gives us RAII, std::string and std::vector internally, while a * internally to make ownership understandable: strings are std::string, decoded
* C or Racket FFI caller still sees a simple C interface. * PCM buffers are std::vector, and FFmpeg objects are released by destructors.
* *
* What this decoder does: * Public design choices:
* *
* 1. Open a media file with FFmpeg/libavformat. * - The caller opens one file.
* 2. Find audio streams. * - The best audio stream is selected internally.
* 3. Read compressed packets from the container. * - FFmpeg stream_index is not exposed.
* 4. Decode packets with the modern avcodec_send_packet() / * - File metadata is stored in the instance and accessed through getters.
* avcodec_receive_frame() API. * - Audio output is always signed 32-bit interleaved PCM.
* 5. Convert decoded audio to one predictable output format: * - There are no callbacks; file IO is handled by FFmpeg.
*
* signed 32-bit integer PCM
* interleaved / packed
* native endian
*
* This is suitable for feeding to libao as 32-bit PCM.
*
* Important FFmpeg vocabulary:
*
* Container/demuxer:
* The file format layer: mp3, mp4/m4a, ogg, wav, etc.
* FFmpeg represents this with AVFormatContext.
*
* Stream:
* A file may contain one or more streams. For this audio-only API we only
* care about streams whose codec_type is AVMEDIA_TYPE_AUDIO.
*
* Packet:
* Compressed data belonging to one stream. One packet may decode to zero,
* one, or multiple decoded frames.
*
* Frame:
* Decoded audio samples, but not necessarily in the format we want. MP3,
* for example, may decode to planar float. We therefore use libswresample
* to normalize everything to signed 32-bit interleaved PCM.
*/ */
#include "ffmpeg_audio.h" #include "ffmpeg_audio.h"
@@ -63,18 +38,8 @@ static constexpr int AC_AUDIO_OUTPUT_BITS = 32;
static constexpr int AC_AUDIO_OUTPUT_BYTES = 4; static constexpr int AC_AUDIO_OUTPUT_BYTES = 4;
static constexpr AVSampleFormat AC_AUDIO_OUTPUT_FMT = AV_SAMPLE_FMT_S32; static constexpr AVSampleFormat AC_AUDIO_OUTPUT_FMT = AV_SAMPLE_FMT_S32;
/* /* Metadata stored inside fmpg_instance. */
* Metadata. struct file_info_storage {
*
* This used to be the kind of place where C code often used fixed-size arrays:
*
* char title[512];
*
* That is simple, but truncates long UTF-8 metadata and wastes space. Since the
* implementation is C++, std::string is the natural representation. The public
* C API only exposes const char* getters.
*/
struct __fmpg_file_info__ {
std::string title; std::string title;
std::string author; std::string author;
std::string album; std::string album;
@@ -84,7 +49,6 @@ struct __fmpg_file_info__ {
int year = -1; int year = -1;
int track = -1; int track = -1;
int64_t duration = -1; /* milliseconds */
int bitrate = -1; int bitrate = -1;
void clear() { void clear() {
@@ -96,21 +60,34 @@ struct __fmpg_file_info__ {
copyright.clear(); copyright.clear();
year = -1; year = -1;
track = -1; track = -1;
duration = -1;
bitrate = -1; bitrate = -1;
} }
}; };
/* /* Audio information for the selected audio stream. */
* __fmpg_instance__ owns the opened media file. struct audio_info_storage {
* int audio_stream_count = 0;
* AVFormatContext is FFmpeg's demuxer/container object. It knows which streams int selected_stream_index = -1; /* Internal FFmpeg stream index. */
* the file contains and can read compressed packets from it. int sample_rate = 0;
*/ int channels = 0;
int64_t duration_ms = -1;
int64_t duration_samples = -1; /* Sample frames, not int32_t values. */
void clear() {
audio_stream_count = 0;
selected_stream_index = -1;
sample_rate = 0;
channels = 0;
duration_ms = -1;
duration_samples = -1;
}
};
struct __fmpg_instance__ { struct __fmpg_instance__ {
bool opened = false; bool opened = false;
AVFormatContext *format_ctx = nullptr; AVFormatContext *format_ctx = nullptr;
fmpg_file_info info; file_info_storage file_info;
audio_info_storage audio_info;
~__fmpg_instance__() { ~__fmpg_instance__() {
if (format_ctx) { if (format_ctx) {
@@ -119,14 +96,7 @@ struct __fmpg_instance__ {
} }
}; };
/*
* A package wraps one FFmpeg AVPacket.
*
* The old Acinerella name was "package". FFmpeg calls this a packet. It is not
* decoded audio yet; it is compressed data read from the container.
*/
struct __fmpg_package__ { struct __fmpg_package__ {
int stream_index = -1;
int64_t pts = AV_NOPTS_VALUE; int64_t pts = AV_NOPTS_VALUE;
AVPacket *packet = nullptr; AVPacket *packet = nullptr;
@@ -137,21 +107,19 @@ struct __fmpg_package__ {
} }
}; };
/*
* __fmpg_decoder__ owns the actual audio decoder and resampler for one stream.
*/
struct __fmpg_decoder__ { struct __fmpg_decoder__ {
fmpg_instance *instance = nullptr; fmpg_instance *instance = nullptr;
int stream_index = -1;
const AVCodec *codec = nullptr; const AVCodec *codec = nullptr;
AVCodecContext *codec_ctx = nullptr; AVCodecContext *codec_ctx = nullptr;
AVFrame *frame = nullptr; AVFrame *frame = nullptr;
SwrContext *swr_ctx = nullptr; SwrContext *swr_ctx = nullptr;
fmpg_audio_info audio_info{};
std::vector<uint8_t> pcm; std::vector<uint8_t> pcm;
double timecode = 0.0; double timecode = 0.0;
int64_t last_samples = 0; /* sample frames in current output block */
int64_t sample_position = 0; /* total sample frames emitted */
~__fmpg_decoder__() { ~__fmpg_decoder__() {
avcodec_free_context(&codec_ctx); avcodec_free_context(&codec_ctx);
@@ -160,26 +128,28 @@ struct __fmpg_decoder__ {
} }
}; };
static const char *empty_if_null(const char *s) { static const char *string_c_str(const std::string &s)
return s ? s : ""; {
}
static const char *string_c_str(const std::string &s) {
return s.empty() ? "" : s.c_str(); return s.empty() ? "" : s.c_str();
} }
static std::string get_metadata_string(const AVFormatContext *ctx, static std::string get_metadata_string(const AVFormatContext *ctx, const char *key)
const char *key) { {
const AVDictionaryEntry *entry = const AVDictionaryEntry *entry = av_dict_get(ctx->metadata,
av_dict_get(ctx->metadata, key, nullptr, 0); key,
nullptr,
0);
return entry && entry->value ? std::string(entry->value) return entry && entry->value ? std::string(entry->value)
: std::string(); : std::string();
} }
static int get_metadata_int(const AVFormatContext *ctx, const char *key) { static int get_metadata_int(const AVFormatContext *ctx, const char *key)
const AVDictionaryEntry *entry = {
av_dict_get(ctx->metadata, key, nullptr, 0); const AVDictionaryEntry *entry = av_dict_get(ctx->metadata,
key,
nullptr,
0);
if (!entry || !entry->value || !*entry->value) { if (!entry || !entry->value || !*entry->value) {
return -1; return -1;
@@ -188,34 +158,134 @@ static int get_metadata_int(const AVFormatContext *ctx, const char *key) {
return std::atoi(entry->value); return std::atoi(entry->value);
} }
static void fill_metadata(fmpg_instance *self) { static int count_audio_streams(const AVFormatContext *ctx)
{
int count = 0;
if (!ctx) {
return 0;
}
for (unsigned i = 0; i < ctx->nb_streams; ++i) {
const AVCodecParameters *par = ctx->streams[i]->codecpar;
if (par && par->codec_type == AVMEDIA_TYPE_AUDIO) {
++count;
}
}
return count;
}
static int64_t milliseconds_from_seconds(double seconds)
{
if (seconds < 0.0) {
return -1;
}
return static_cast<int64_t>(seconds * 1000.0 + 0.5);
}
static int64_t samples_from_seconds(double seconds, int sample_rate)
{
if (seconds < 0.0 || sample_rate <= 0) {
return -1;
}
return static_cast<int64_t>(seconds * static_cast<double>(sample_rate) +
0.5);
}
static double stream_duration_seconds(const AVStream *stream)
{
if (!stream || stream->duration == AV_NOPTS_VALUE) {
return -1.0;
}
return static_cast<double>(stream->duration) * av_q2d(stream->time_base);
}
static double format_duration_seconds(const AVFormatContext *ctx)
{
if (!ctx || ctx->duration == AV_NOPTS_VALUE) {
return -1.0;
}
return static_cast<double>(ctx->duration) / static_cast<double>(AV_TIME_BASE);
}
static void fill_file_metadata(fmpg_instance *self)
{
AVFormatContext *ctx = self->format_ctx; AVFormatContext *ctx = self->format_ctx;
self->info.clear(); self->file_info.clear();
self->info.title = get_metadata_string(ctx, "title"); self->file_info.title = get_metadata_string(ctx, "title");
self->info.author = get_metadata_string(ctx, "artist"); self->file_info.author = get_metadata_string(ctx, "artist");
self->info.album = get_metadata_string(ctx, "album"); self->file_info.album = get_metadata_string(ctx, "album");
self->info.genre = get_metadata_string(ctx, "genre"); self->file_info.genre = get_metadata_string(ctx, "genre");
self->info.comment = get_metadata_string(ctx, "comment"); self->file_info.comment = get_metadata_string(ctx, "comment");
self->info.copyright = get_metadata_string(ctx, "copyright"); self->file_info.copyright = get_metadata_string(ctx, "copyright");
self->info.year = get_metadata_int(ctx, "year"); self->file_info.year = get_metadata_int(ctx, "year");
self->info.track = get_metadata_int(ctx, "track"); self->file_info.track = get_metadata_int(ctx, "track");
self->info.bitrate = static_cast<int>(ctx->bit_rate); self->file_info.bitrate = ctx->bit_rate > 0
? static_cast<int>(ctx->bit_rate)
self->info.duration = : -1;
ctx->duration == AV_NOPTS_VALUE
? -1
: ctx->duration * 1000 / AV_TIME_BASE;
} }
static bool valid_stream_index(const fmpg_instance *instance, int stream_index) static bool fill_audio_info(fmpg_instance *self)
{
AVFormatContext *ctx = self->format_ctx;
self->audio_info.clear();
self->audio_info.audio_stream_count = count_audio_streams(ctx);
const int best = av_find_best_stream(ctx,
AVMEDIA_TYPE_AUDIO,
-1,
-1,
nullptr,
0);
if (best < 0) {
return false;
}
AVStream *stream = ctx->streams[best];
const AVCodecParameters *par = stream->codecpar;
if (!par || par->codec_type != AVMEDIA_TYPE_AUDIO ||
par->sample_rate <= 0 || par->ch_layout.nb_channels <= 0) {
return false;
}
self->audio_info.selected_stream_index = best;
self->audio_info.sample_rate = par->sample_rate;
self->audio_info.channels = par->ch_layout.nb_channels;
/*
* Duration can come from the selected audio stream or from the container.
* Stream duration is preferred because it is tied to the audio stream's own
* time base. Some containers only provide container-level duration, so that
* is the fallback.
*/
double seconds = stream_duration_seconds(stream);
if (seconds < 0.0) {
seconds = format_duration_seconds(ctx);
}
self->audio_info.duration_ms = milliseconds_from_seconds(seconds);
self->audio_info.duration_samples =
samples_from_seconds(seconds, self->audio_info.sample_rate);
return true;
}
static bool instance_ready(const fmpg_instance *instance)
{ {
return instance && instance->opened && instance->format_ctx && return instance && instance->opened && instance->format_ctx &&
stream_index >= 0 && instance->audio_info.selected_stream_index >= 0;
stream_index < static_cast<int>(instance->format_ctx->nb_streams);
} }
fmpg_instance * ac_init(void) { fmpg_instance *fmpg_init(void) {
try { try {
return new fmpg_instance(); return new fmpg_instance();
} catch (...) { } catch (...) {
@@ -223,43 +293,43 @@ fmpg_instance * ac_init(void) {
} }
} }
void ac_free(fmpg_instance * instance) { void fmpg_free(fmpg_instance *instance)
{
delete instance; delete instance;
} }
int ac_open_file(fmpg_instance * instance, int fmpg_open_file(fmpg_instance *instance, const char *filename)
const char *filename) { {
if (!instance || instance->opened || !filename) { if (!instance || instance->opened || !filename) {
return 0; return 0;
} }
/*
* avformat_open_input opens the file and guesses the container format.
* The codec is not opened here. This is only the demuxing layer.
*/
if (avformat_open_input(&instance->format_ctx, if (avformat_open_input(&instance->format_ctx,
empty_if_null(filename), filename,
nullptr, nullptr,
nullptr) < 0) { nullptr) < 0) {
ac_close(instance); fmpg_close(instance);
return 0; return 0;
} }
/*
* Read enough packets to discover stream metadata such as sample rate,
* channel layout, codec id, duration and tags.
*/
if (avformat_find_stream_info(instance->format_ctx, nullptr) < 0) { if (avformat_find_stream_info(instance->format_ctx, nullptr) < 0) {
ac_close(instance); fmpg_close(instance);
return 0;
}
fill_file_metadata(instance);
if (!fill_audio_info(instance)) {
fmpg_close(instance);
return 0; return 0;
} }
fill_metadata(instance);
instance->opened = true; instance->opened = true;
return 1; return 1;
} }
void ac_close(fmpg_instance * instance) { void fmpg_close(fmpg_instance *instance)
{
if (!instance) { if (!instance) {
return; return;
} }
@@ -269,138 +339,103 @@ void ac_close(fmpg_instance * instance) {
} }
instance->opened = false; instance->opened = false;
instance->info.clear(); instance->file_info.clear();
instance->audio_info.clear();
} }
int ac_is_open(fmpg_instance * instance) int fmpg_is_open(fmpg_instance *instance)
{ {
return instance && instance->opened ? 1 : 0; return instance_ready(instance) ? 1 : 0;
} }
int ac_get_audio_stream_count(fmpg_instance * instance) int fmpg_audio_stream_count(fmpg_instance *instance)
{ {
if (!instance || !instance->opened || !instance->format_ctx) { return instance && instance->opened ? instance->audio_info.audio_stream_count
return 0; : 0;
}
int count = 0;
for (unsigned i = 0; i < instance->format_ctx->nb_streams; ++i) {
const AVCodecParameters *par =
instance->format_ctx->streams[i]->codecpar;
if (par && par->codec_type == AVMEDIA_TYPE_AUDIO) {
++count;
}
}
return count;
} }
int ac_get_default_audio_stream(fmpg_instance * instance) int fmpg_audio_sample_rate(fmpg_instance *instance)
{ {
if (!instance || !instance->opened || !instance->format_ctx) { return instance_ready(instance) ? instance->audio_info.sample_rate : 0;
return -1;
}
const int idx = av_find_best_stream(instance->format_ctx,
AVMEDIA_TYPE_AUDIO,
-1,
-1,
nullptr,
0);
return idx >= 0 ? idx : -1;
} }
int ac_get_audio_info(fmpg_instance * instance, int stream_index, fmpg_audio_info *info) int fmpg_audio_channels(fmpg_instance *instance)
{ {
if (!info) { return instance_ready(instance) ? instance->audio_info.channels : 0;
return 0;
}
std::memset(info, 0, sizeof(*info));
if (!valid_stream_index(instance, stream_index)) {
return 0;
}
const AVCodecParameters *par =
instance->format_ctx->streams[stream_index]->codecpar;
if (!par || par->codec_type != AVMEDIA_TYPE_AUDIO) {
return 0;
}
info->sample_rate = par->sample_rate;
info->channels = par->ch_layout.nb_channels;
info->bits_per_sample = AC_AUDIO_OUTPUT_BITS;
info->bytes_per_sample = AC_AUDIO_OUTPUT_BYTES;
return info->sample_rate > 0 && info->channels > 0 ? 1 : 0;
} }
const fmpg_file_info *ac_get_file_info(fmpg_instance * instance) int fmpg_audio_bits_per_sample(fmpg_instance *)
{ {
return instance ? &instance->info : nullptr; return AC_AUDIO_OUTPUT_BITS;
} }
const char * ac_file_info_title(const fmpg_file_info *info) int fmpg_audio_bytes_per_sample(fmpg_instance *)
{ {
return info ? string_c_str(info->title) : ""; return AC_AUDIO_OUTPUT_BYTES;
} }
const char *ac_file_info_author(const fmpg_file_info *info) int64_t fmpg_duration_ms(fmpg_instance *instance)
{ {
return info ? string_c_str(info->author) : ""; return instance_ready(instance) ? instance->audio_info.duration_ms : -1;
} }
const char *ac_file_info_album(const fmpg_file_info *info) int64_t fmpg_duration_samples(fmpg_instance *instance)
{ {
return info ? string_c_str(info->album) : ""; return instance_ready(instance) ? instance->audio_info.duration_samples : -1;
} }
const char *ac_file_info_genre(const fmpg_file_info *info) const char *fmpg_file_title(fmpg_instance *instance)
{ {
return info ? string_c_str(info->genre) : ""; return instance ? string_c_str(instance->file_info.title) : "";
} }
const char *ac_file_info_comment(const fmpg_file_info *info) const char *fmpg_file_author(fmpg_instance *instance)
{ {
return info ? string_c_str(info->comment) : ""; return instance ? string_c_str(instance->file_info.author) : "";
} }
const char *ac_file_info_copyright(const fmpg_file_info *info) const char *fmpg_file_album(fmpg_instance *instance) {
{ return instance ? string_c_str(instance->file_info.album) : "";
return info ? string_c_str(info->copyright) : "";
} }
int ac_file_info_year(const fmpg_file_info *info) const char *fmpg_file_genre(fmpg_instance *instance)
{ {
return info ? info->year : -1; return instance ? string_c_str(instance->file_info.genre) : "";
} }
int ac_file_info_track(const fmpg_file_info *info) const char *fmpg_file_comment(fmpg_instance *instance)
{ {
return info ? info->track : -1; return instance ? string_c_str(instance->file_info.comment) : "";
} }
int64_t ac_file_info_duration(const fmpg_file_info *info) const char *fmpg_file_copyright(fmpg_instance *instance)
{ {
return info ? info->duration : -1; return instance ? string_c_str(instance->file_info.copyright) : "";
} }
int ac_file_info_bitrate(const fmpg_file_info *info) int fmpg_file_year(fmpg_instance *instance) {
{ return instance ? instance->file_info.year : -1;
return info ? info->bitrate : -1;
} }
fmpg_package * ac_read_package(fmpg_instance * instance) int fmpg_file_track(fmpg_instance *instance)
{ {
if (!instance || !instance->opened || !instance->format_ctx) { return instance ? instance->file_info.track : -1;
}
int fmpg_file_bitrate(fmpg_instance *instance)
{
return instance ? instance->file_info.bitrate : -1;
}
fmpg_package *fmpg_read_package(fmpg_instance *instance)
{
if (!instance_ready(instance)) {
return nullptr; return nullptr;
} }
const int wanted_stream = instance->audio_info.selected_stream_index;
for (;;) {
fmpg_package *pkg = nullptr; fmpg_package *pkg = nullptr;
try { try {
@@ -414,34 +449,29 @@ fmpg_package * ac_read_package(fmpg_instance * instance)
return nullptr; return nullptr;
} }
/* const int ret = av_read_frame(instance->format_ctx, pkg->packet);
* av_read_frame reads one compressed packet. This may be audio, video, if (ret < 0) {
* subtitles, or another stream type. The caller can inspect stream_index
* and only feed audio packets to the matching decoder.
*/
if (av_read_frame(instance->format_ctx, pkg->packet) < 0) {
delete pkg; delete pkg;
return nullptr; return nullptr;
} }
pkg->stream_index = pkg->packet->stream_index; if (pkg->packet->stream_index != wanted_stream) {
delete pkg;
continue;
}
pkg->pts = pkg->packet->dts != AV_NOPTS_VALUE pkg->pts = pkg->packet->dts != AV_NOPTS_VALUE
? pkg->packet->dts ? pkg->packet->dts
: pkg->packet->pts; : pkg->packet->pts;
return pkg; return pkg;
}
} }
void ac_free_package(fmpg_package * package) void fmpg_free_package(fmpg_package *package)
{ {
delete package; delete package;
} }
int ac_package_stream_index(fmpg_package * package)
{
return package ? package->stream_index : -1;
}
static bool init_codec_context(fmpg_decoder *dec, const AVCodecParameters *par) static bool init_codec_context(fmpg_decoder *dec, const AVCodecParameters *par)
{ {
dec->codec = avcodec_find_decoder(par->codec_id); dec->codec = avcodec_find_decoder(par->codec_id);
@@ -454,21 +484,11 @@ static bool init_codec_context(fmpg_decoder *dec, const AVCodecParameters *par)
return false; return false;
} }
/*
* Copy stream codec parameters into the active decoder context.
*/
if (avcodec_parameters_to_context(dec->codec_ctx, par) < 0) { if (avcodec_parameters_to_context(dec->codec_ctx, par) < 0) {
return false; return false;
} }
/* return avcodec_open2(dec->codec_ctx, dec->codec, nullptr) >= 0;
* Open the actual decoder. From this point on, packets can be sent to it.
*/
if (avcodec_open2(dec->codec_ctx, dec->codec, nullptr) < 0) {
return false;
}
return true;
} }
static bool init_resampler(fmpg_decoder *dec) static bool init_resampler(fmpg_decoder *dec)
@@ -479,10 +499,6 @@ static bool init_resampler(fmpg_decoder *dec)
return false; return false;
} }
/*
* We do not change sample rate or channel layout. We only normalize the
* sample format to signed 32-bit integer PCM.
*/
if (swr_alloc_set_opts2(&dec->swr_ctx, if (swr_alloc_set_opts2(&dec->swr_ctx,
layout, layout,
AC_AUDIO_OUTPUT_FMT, AC_AUDIO_OUTPUT_FMT,
@@ -498,14 +514,9 @@ static bool init_resampler(fmpg_decoder *dec)
return swr_init(dec->swr_ctx) >= 0; return swr_init(dec->swr_ctx) >= 0;
} }
fmpg_decoder * ac_create_decoder(fmpg_instance * instance, int stream_index) fmpg_decoder *fmpg_create_decoder(fmpg_instance *instance)
{ {
if (!valid_stream_index(instance, stream_index)) { if (!instance_ready(instance)) {
return nullptr;
}
fmpg_audio_info info{};
if (!ac_get_audio_info(instance, stream_index, &info)) {
return nullptr; return nullptr;
} }
@@ -518,11 +529,9 @@ fmpg_decoder * ac_create_decoder(fmpg_instance * instance, int stream_index)
} }
dec->instance = instance; dec->instance = instance;
dec->stream_index = stream_index;
dec->audio_info = info;
const AVCodecParameters *par = const int stream_index = instance->audio_info.selected_stream_index;
instance->format_ctx->streams[stream_index]->codecpar; const AVCodecParameters *par = instance->format_ctx->streams[stream_index]->codecpar;
if (!init_codec_context(dec, par)) { if (!init_codec_context(dec, par)) {
delete dec; delete dec;
@@ -543,7 +552,7 @@ fmpg_decoder * ac_create_decoder(fmpg_instance * instance, int stream_index)
return dec; return dec;
} }
void ac_free_decoder(fmpg_decoder * decoder) void fmpg_free_decoder(fmpg_decoder *decoder)
{ {
delete decoder; delete decoder;
} }
@@ -577,25 +586,17 @@ static bool append_converted_frame(fmpg_decoder *dec, const AVFrame *frame)
return true; return true;
} }
/* const int max_out_samples = swr_get_out_samples(dec->swr_ctx,
* swr_get_out_samples gives a safe upper bound for the number of output frame->nb_samples);
* samples. The resampler can have internal delay, so this is safer than
* assuming input sample count equals output sample count.
*/
const int max_out_samples =
swr_get_out_samples(dec->swr_ctx, frame->nb_samples);
if (max_out_samples <= 0) { if (max_out_samples <= 0) {
return false; return false;
} }
const int max_bytes = const int max_bytes = av_samples_get_buffer_size(nullptr,
av_samples_get_buffer_size(nullptr,
channels, channels,
max_out_samples, max_out_samples,
AC_AUDIO_OUTPUT_FMT, AC_AUDIO_OUTPUT_FMT,
1); 1);
if (max_bytes <= 0) { if (max_bytes <= 0) {
return false; return false;
} }
@@ -603,32 +604,31 @@ static bool append_converted_frame(fmpg_decoder *dec, const AVFrame *frame)
std::vector<uint8_t> tmp(static_cast<size_t>(max_bytes)); std::vector<uint8_t> tmp(static_cast<size_t>(max_bytes));
uint8_t *out_planes[1] = { tmp.data() }; uint8_t *out_planes[1] = { tmp.data() };
/* const int out_samples = swr_convert(dec->swr_ctx,
* swr_convert performs the actual conversion to S32 interleaved PCM.
*/
const int out_samples =
swr_convert(dec->swr_ctx,
out_planes, out_planes,
max_out_samples, max_out_samples,
const_cast<const uint8_t **>(frame->data), const_cast<const uint8_t **>(frame->data),
frame->nb_samples); frame->nb_samples);
if (out_samples < 0) { if (out_samples < 0) {
return false; return false;
} }
const int used_bytes = const int used_bytes = av_samples_get_buffer_size(nullptr,
av_samples_get_buffer_size(nullptr,
channels, channels,
out_samples, out_samples,
AC_AUDIO_OUTPUT_FMT, AC_AUDIO_OUTPUT_FMT,
1); 1);
if (used_bytes < 0) { if (used_bytes < 0) {
return false; return false;
} }
return append_bytes(dec, tmp.data(), static_cast<size_t>(used_bytes)); if (!append_bytes(dec, tmp.data(), static_cast<size_t>(used_bytes))) {
return false;
}
dec->last_samples += out_samples;
dec->sample_position += out_samples;
return true;
} }
static int receive_available_frames(fmpg_decoder *dec) static int receive_available_frames(fmpg_decoder *dec)
@@ -662,29 +662,21 @@ static void update_timecode_from_packet(fmpg_decoder *dec, const fmpg_package *p
return; return;
} }
AVStream *stream = dec->instance->format_ctx->streams[pkg->stream_index]; const int stream_index = dec->instance->audio_info.selected_stream_index;
AVStream *stream = dec->instance->format_ctx->streams[stream_index];
dec->timecode = pkg->pts * av_q2d(stream->time_base); dec->timecode = pkg->pts * av_q2d(stream->time_base);
} }
int ac_decode_package(fmpg_package * package, fmpg_decoder * decoder) int fmpg_decode_package(fmpg_package *package, fmpg_decoder *decoder)
{ {
if (!package || !decoder || !package->packet || if (!package || !decoder || !package->packet) {
package->stream_index != decoder->stream_index) {
return 0; return 0;
} }
decoder->pcm.clear(); decoder->pcm.clear();
decoder->last_samples = 0;
update_timecode_from_packet(decoder, package); update_timecode_from_packet(decoder, package);
/*
* Modern FFmpeg decoding is a two-step queue-like API:
*
* 1. send compressed packet
* 2. receive all decoded frames currently available
*
* A single packet can produce multiple frames, especially with codecs that
* buffer internally. We concatenate all produced PCM blocks.
*/
int ret = avcodec_send_packet(decoder->codec_ctx, package->packet); int ret = avcodec_send_packet(decoder->codec_ctx, package->packet);
if (ret == AVERROR(EAGAIN)) { if (ret == AVERROR(EAGAIN)) {
@@ -701,18 +693,15 @@ int ac_decode_package(fmpg_package * package, fmpg_decoder * decoder)
return receive_available_frames(decoder) > 0 ? 1 : 0; return receive_available_frames(decoder) > 0 ? 1 : 0;
} }
int ac_flush_decoder(fmpg_decoder * decoder) int fmpg_flush_decoder(fmpg_decoder *decoder)
{ {
if (!decoder) { if (!decoder) {
return 0; return 0;
} }
decoder->pcm.clear(); decoder->pcm.clear();
decoder->last_samples = 0;
/*
* Sending NULL tells FFmpeg that no more input is coming and that delayed
* decoded frames should be drained.
*/
const int ret = avcodec_send_packet(decoder->codec_ctx, nullptr); const int ret = avcodec_send_packet(decoder->codec_ctx, nullptr);
if (ret < 0 && ret != AVERROR_EOF) { if (ret < 0 && ret != AVERROR_EOF) {
return 0; return 0;
@@ -723,25 +712,20 @@ int ac_flush_decoder(fmpg_decoder * decoder)
return 0; return 0;
} }
/* Drain possible delayed samples from libswresample as well. */
const int channels = decoder->codec_ctx->ch_layout.nb_channels; const int channels = decoder->codec_ctx->ch_layout.nb_channels;
for (;;) { for (;;) {
const int delay = const int delay = static_cast<int>(swr_get_delay(decoder->swr_ctx,
static_cast<int>(swr_get_delay(decoder->swr_ctx,
decoder->codec_ctx->sample_rate)); decoder->codec_ctx->sample_rate));
if (delay <= 0) { if (delay <= 0) {
break; break;
} }
const int max_bytes = const int max_bytes = av_samples_get_buffer_size(nullptr,
av_samples_get_buffer_size(nullptr,
channels, channels,
delay, delay,
AC_AUDIO_OUTPUT_FMT, AC_AUDIO_OUTPUT_FMT,
1); 1);
if (max_bytes <= 0) { if (max_bytes <= 0) {
break; break;
} }
@@ -749,46 +733,50 @@ int ac_flush_decoder(fmpg_decoder * decoder)
std::vector<uint8_t> tmp(static_cast<size_t>(max_bytes)); std::vector<uint8_t> tmp(static_cast<size_t>(max_bytes));
uint8_t *out_planes[1] = { tmp.data() }; uint8_t *out_planes[1] = { tmp.data() };
const int out_samples = const int out_samples = swr_convert(decoder->swr_ctx,
swr_convert(decoder->swr_ctx,
out_planes, out_planes,
delay, delay,
nullptr, nullptr,
0); 0);
if (out_samples <= 0) { if (out_samples <= 0) {
break; break;
} }
const int used_bytes = const int used_bytes = av_samples_get_buffer_size(nullptr,
av_samples_get_buffer_size(nullptr,
channels, channels,
out_samples, out_samples,
AC_AUDIO_OUTPUT_FMT, AC_AUDIO_OUTPUT_FMT,
1); 1);
if (used_bytes < 0 || if (used_bytes < 0 ||
!append_bytes(decoder, tmp.data(), static_cast<size_t>(used_bytes))) { !append_bytes(decoder,
tmp.data(),
static_cast<size_t>(used_bytes))) {
break; break;
} }
decoder->last_samples += out_samples;
decoder->sample_position += out_samples;
} }
return decoder->pcm.empty() ? 0 : 1; return decoder->pcm.empty() ? 0 : 1;
} }
int ac_seek_ms(fmpg_decoder * decoder, int64_t target_pos_ms) int fmpg_seek_ms(fmpg_decoder *decoder, int64_t target_pos_ms)
{ {
if (!decoder || !decoder->instance || !decoder->instance->format_ctx) { if (!decoder || !instance_ready(decoder->instance)) {
return 0; return 0;
} }
AVStream *stream = decoder->instance->format_ctx->streams[decoder->stream_index]; const int stream_index = decoder->instance->audio_info.selected_stream_index;
AVStream *stream = decoder->instance->format_ctx->streams[stream_index];
const int64_t pos_us = av_rescale(target_pos_ms, AV_TIME_BASE, 1000); const int64_t pos_us = av_rescale(target_pos_ms, AV_TIME_BASE, 1000);
const int64_t stream_ts = av_rescale_q(pos_us, AV_TIME_BASE_Q, stream->time_base); const int64_t stream_ts = av_rescale_q(pos_us,
AV_TIME_BASE_Q,
stream->time_base);
if (av_seek_frame(decoder->instance->format_ctx, if (av_seek_frame(decoder->instance->format_ctx,
decoder->stream_index, stream_index,
stream_ts, stream_ts,
AVSEEK_FLAG_BACKWARD) < 0) { AVSEEK_FLAG_BACKWARD) < 0) {
return 0; return 0;
@@ -796,24 +784,23 @@ int ac_seek_ms(fmpg_decoder * decoder, int64_t target_pos_ms)
decoder->timecode = target_pos_ms / 1000.0; decoder->timecode = target_pos_ms / 1000.0;
decoder->pcm.clear(); decoder->pcm.clear();
decoder->last_samples = 0;
decoder->sample_position = samples_from_seconds(decoder->timecode,
decoder->instance->audio_info.sample_rate);
/* Old buffered data no longer belongs to the new seek position. */
avcodec_flush_buffers(decoder->codec_ctx); avcodec_flush_buffers(decoder->codec_ctx);
/* Reset resampler delay/state too. */
swr_close(decoder->swr_ctx); swr_close(decoder->swr_ctx);
return swr_init(decoder->swr_ctx) >= 0 ? 1 : 0; return swr_init(decoder->swr_ctx) >= 0 ? 1 : 0;
} }
const uint8_t *ac_decoder_buffer(fmpg_decoder * decoder) const uint8_t *fmpg_decoder_buffer(fmpg_decoder *decoder)
{ {
return decoder && !decoder->pcm.empty() ? decoder->pcm.data() : nullptr; return decoder && !decoder->pcm.empty() ? decoder->pcm.data() : nullptr;
} }
int ac_decoder_buffer_size(fmpg_decoder * decoder) int fmpg_decoder_buffer_size(fmpg_decoder *decoder) {
{ if (!decoder || decoder->pcm.size() >
if (!decoder ||
decoder->pcm.size() >
static_cast<size_t>(std::numeric_limits<int>::max())) { static_cast<size_t>(std::numeric_limits<int>::max())) {
return 0; return 0;
} }
@@ -821,12 +808,17 @@ int ac_decoder_buffer_size(fmpg_decoder * decoder)
return static_cast<int>(decoder->pcm.size()); return static_cast<int>(decoder->pcm.size());
} }
double ac_decoder_timecode(fmpg_decoder * decoder) double fmpg_decoder_timecode(fmpg_decoder *decoder)
{ {
return decoder ? decoder->timecode : 0.0; return decoder ? decoder->timecode : 0.0;
} }
int ac_decoder_stream_index(fmpg_decoder * decoder) int64_t fmpg_decoder_last_samples(fmpg_decoder *decoder)
{ {
return decoder ? decoder->stream_index : -1; return decoder ? decoder->last_samples : 0;
}
int64_t fmpg_decoder_sample_position(fmpg_decoder *decoder)
{
return decoder ? decoder->sample_position : 0;
} }
+219 -67
View File
@@ -18,93 +18,245 @@ extern "C" {
#endif #endif
/* /*
* Audio-only Acinerella API. * Audio-only FFmpeg wrapper.
* *
* The implementation may be C++, but this header is plain C-compatible. * The implementation is C++, but this header is plain C-compatible. All
* All public structs below are opaque handles, except ac_audio_info. * public object types are opaque. The caller never sees FFmpeg's AVFormatContext,
* AVCodecContext, AVPacket, stream_index, or std::string objects.
*
* Output audio format is deliberately fixed:
*
* signed 32-bit integer PCM
* interleaved / packed
* native endian
*
* This makes the API easy to bind from Racket and straightforward to feed into
* libao. Source formats such as MP3 float/planar output are converted internally.
*/ */
typedef struct __fmpg_instance__ fmpg_instance; typedef struct __fmpg_instance__ fmpg_instance;
typedef struct __fmpg_decoder__ fmpg_decoder; typedef struct __fmpg_decoder__ fmpg_decoder;
typedef struct __fmpg_package__ fmpg_package; typedef struct __fmpg_package__ fmpg_package;
typedef struct __fmpg_file_info__ fmpg_file_info;
typedef struct __fmpg_audio_info__ {
int sample_rate;
int channels;
int bits_per_sample; /* Always 32. */
int bytes_per_sample; /* Always 4. */
} fmpg_audio_info;
/* ------------------------------------------------------------------------- */
/* Lifecycle */ /* Lifecycle */
FFMPEG_EXTERN fmpg_instance * ac_init(void); /* ------------------------------------------------------------------------- */
FFMPEG_EXTERN void ac_free(fmpg_instance * instance);
FFMPEG_EXTERN int ac_open_file(fmpg_instance * instance, const char *filename);
FFMPEG_EXTERN void ac_close(fmpg_instance * instance);
FFMPEG_EXTERN int ac_is_open(fmpg_instance * instance);
/* Audio stream discovery */
FFMPEG_EXTERN int ac_get_audio_stream_count(fmpg_instance * instance);
FFMPEG_EXTERN int ac_get_default_audio_stream(fmpg_instance * instance);
FFMPEG_EXTERN int ac_get_audio_info(fmpg_instance * instance, int stream_index, fmpg_audio_info *info);
/* Metadata. The returned strings are owned by the instance. */
FFMPEG_EXTERN const fmpg_file_info * ac_get_file_info(fmpg_instance * instance);
FFMPEG_EXTERN const char * ac_file_info_title(const fmpg_file_info *info);
FFMPEG_EXTERN const char * ac_file_info_author(const fmpg_file_info *info);
FFMPEG_EXTERN const char * ac_file_info_album(const fmpg_file_info *info);
FFMPEG_EXTERN const char * ac_file_info_genre(const fmpg_file_info *info);
FFMPEG_EXTERN const char * ac_file_info_comment(const fmpg_file_info *info);
FFMPEG_EXTERN const char * ac_file_info_copyright(const fmpg_file_info *info);
FFMPEG_EXTERN int ac_file_info_year(const fmpg_file_info *info);
FFMPEG_EXTERN int ac_file_info_track(const fmpg_file_info *info);
FFMPEG_EXTERN int64_t ac_file_info_duration(const fmpg_file_info *info);
FFMPEG_EXTERN int ac_file_info_bitrate(const fmpg_file_info *info);
/* Packet reading */
FFMPEG_EXTERN fmpg_package * ac_read_package(fmpg_instance * instance);
FFMPEG_EXTERN void ac_free_package(fmpg_package * package);
FFMPEG_EXTERN int ac_package_stream_index(fmpg_package * package);
/* Decoder */
FFMPEG_EXTERN fmpg_decoder * ac_create_decoder(fmpg_instance * instance, int stream_index);
FFMPEG_EXTERN void ac_free_decoder(fmpg_decoder * decoder);
/* /*
* Decode one compressed packet. * Create an empty decoder instance.
* *
* Returns 1 if PCM data was produced, 0 otherwise. * Return:
* * instance pointer, or NULL on allocation failure.
* Output format:
* signed 32-bit integer PCM
* interleaved
* native endian
*
* Example stereo layout:
* L0 R0 L1 R1 L2 R2 ...
*/ */
FFMPEG_EXTERN int ac_decode_package(fmpg_package * package, fmpg_decoder * decoder); FFMPEG_EXTERN fmpg_instance *fmpg_init(void);
/*
* Close any open file and free the instance.
*
* It is safe to pass NULL.
*/
FFMPEG_EXTERN void fmpg_free(fmpg_instance *instance);
/*
* Open a media file and select the best audio stream.
*
* The selected stream index is kept inside the instance. The public API does
* not expose FFmpeg stream indices. After this function succeeds, metadata,
* duration, sample rate and channel count are available through the getters
* below.
*
* Return:
* 1 on success
* 0 on failure or if no usable audio stream was found
*/
FFMPEG_EXTERN int fmpg_open_file(fmpg_instance *instance,
const char *filename);
/* Close the current file, if any, and reset instance-owned information. */
FFMPEG_EXTERN void fmpg_close(fmpg_instance *instance);
/* Return 1 if a file is open, 0 otherwise. */
FFMPEG_EXTERN int fmpg_is_open(fmpg_instance *instance);
/* ------------------------------------------------------------------------- */
/* Audio information */
/* ------------------------------------------------------------------------- */
/*
* The number of audio streams found in the container.
*
* The decoder currently uses the best stream selected by FFmpeg. This count is
* informational; stream selection is intentionally not part of the public API.
*/
FFMPEG_EXTERN int fmpg_audio_stream_count(fmpg_instance *instance);
/* Output sample rate in Hz, for example 44100 or 48000. */
FFMPEG_EXTERN int fmpg_audio_sample_rate(fmpg_instance *instance);
/* Number of output channels, for example 1 or 2. */
FFMPEG_EXTERN int fmpg_audio_channels(fmpg_instance *instance);
/* Always 32: samples are signed 32-bit integer PCM. */
FFMPEG_EXTERN int fmpg_audio_bits_per_sample(fmpg_instance *instance);
/* Always 4: one output sample occupies four bytes. */
FFMPEG_EXTERN int fmpg_audio_bytes_per_sample(fmpg_instance *instance);
/*
* Duration in milliseconds, or -1 if unknown.
*
* This value is known after ac_open_file() succeeds, as far as FFmpeg can know
* it from the container/stream metadata. Some streams do not contain exact
* duration information; in that case this getter returns -1.
*/
FFMPEG_EXTERN int64_t fmpg_duration_ms(fmpg_instance *instance);
/*
* Duration expressed as output sample frames, or -1 if unknown.
*
* A sample frame means one sample moment across all channels. For stereo, one
* sample frame contains two int32_t values: left and right. This is usually the
* most useful duration unit for playback and progress calculations.
*
* PCM int32_t values in the whole output would be:
*
* ac_duration_samples(instance) * ac_audio_channels(instance)
*/
FFMPEG_EXTERN int64_t fmpg_duration_samples(fmpg_instance *instance);
/* ------------------------------------------------------------------------- */
/* Metadata */
/* ------------------------------------------------------------------------- */
/*
* Metadata is owned by the instance and available after ac_open_file().
* Returned strings are never NULL. Missing metadata is returned as "".
*
* Pointers remain valid until ac_close() or ac_free() is called for the
* instance. Do not free the returned strings.
*/
FFMPEG_EXTERN const char *fmpg_file_title(fmpg_instance *instance);
FFMPEG_EXTERN const char *fmpg_file_author(fmpg_instance *instance);
FFMPEG_EXTERN const char *fmpg_file_album(fmpg_instance *instance);
FFMPEG_EXTERN const char *fmpg_file_genre(fmpg_instance *instance);
FFMPEG_EXTERN const char *fmpg_file_comment(fmpg_instance *instance);
FFMPEG_EXTERN const char *fmpg_file_copyright(fmpg_instance *instance);
/* Return -1 if the field is unknown. */
FFMPEG_EXTERN int fmpg_file_year(fmpg_instance *instance);
FFMPEG_EXTERN int fmpg_file_track(fmpg_instance *instance);
/* Container-level bitrate in bits/second, or -1 if unknown. */
FFMPEG_EXTERN int fmpg_file_bitrate(fmpg_instance *instance);
/* ------------------------------------------------------------------------- */
/* Packet reading */
/* ------------------------------------------------------------------------- */
/*
* Read the next compressed packet from the selected audio stream.
*
* Non-audio packets and packets from non-selected streams are skipped
* internally. The caller therefore no longer has to inspect stream_index.
*
* Return:
* package pointer, or NULL at EOF or on read error.
*/
FFMPEG_EXTERN fmpg_package *fmpg_read_package(fmpg_instance *instance);
/* Free a package returned by ac_read_package(). Safe to pass NULL. */
FFMPEG_EXTERN void fmpg_free_package(fmpg_package *package);
/* ------------------------------------------------------------------------- */
/* Decoder */
/* ------------------------------------------------------------------------- */
/*
* Create a decoder for the selected audio stream.
*
* The stream is the one selected during ac_open_file(). The caller does not
* pass a stream index.
*/
FFMPEG_EXTERN fmpg_decoder *fmpg_create_decoder(fmpg_instance *instance);
/* Free decoder and all FFmpeg decoder/resampler state. Safe to pass NULL. */
FFMPEG_EXTERN void fmpg_free_decoder(fmpg_decoder *decoder);
/*
* Decode one compressed audio package.
*
* Modern FFmpeg decoding is packet-in, frame-out. One compressed packet can
* produce zero, one, or multiple decoded frames. This function receives all
* available frames, converts them to signed 32-bit interleaved PCM, and
* concatenates them into the decoder output buffer.
*
* Return:
* 1 if PCM data was produced
* 0 if no PCM data was produced or an error occurred
*/
FFMPEG_EXTERN int fmpg_decode_package(fmpg_package *package, fmpg_decoder *decoder);
/* /*
* Flush delayed decoder/resampler samples after EOF. * Flush delayed decoder/resampler samples after EOF.
* *
* Call this repeatedly after ac_read_package() returns NULL, * Call this repeatedly after ac_read_package() returns NULL, until this
* until it returns 0. * function returns 0.
*/ */
FFMPEG_EXTERN int ac_flush_decoder(fmpg_decoder * decoder); FFMPEG_EXTERN int fmpg_flush_decoder(fmpg_decoder *decoder);
/* Seek to absolute position in milliseconds. */ /*
FFMPEG_EXTERN int ac_seek_ms(fmpg_decoder * decoder, int64_t target_pos_ms); * Seek to an absolute position in milliseconds.
*
* The compressed decoder buffer, decoded output buffer and resampler state are
* reset. After seeking, continue reading packages and decoding as usual.
*
* Return:
* 1 on success
* 0 on failure
*/
FFMPEG_EXTERN int fmpg_seek_ms(fmpg_decoder *decoder, int64_t target_pos_ms);
/* ------------------------------------------------------------------------- */
/* Decoder output */ /* Decoder output */
FFMPEG_EXTERN const uint8_t * ac_decoder_buffer(fmpg_decoder * decoder); /* ------------------------------------------------------------------------- */
FFMPEG_EXTERN int ac_decoder_buffer_size(fmpg_decoder * decoder);
FFMPEG_EXTERN double ac_decoder_timecode(fmpg_decoder * decoder); /*
FFMPEG_EXTERN int ac_decoder_stream_index(fmpg_decoder * decoder); * Pointer to the current decoded PCM buffer.
*
* Format:
* int32_t samples
* interleaved by channel
* native endian
*
* The pointer remains valid until the next ac_decode_package(),
* ac_flush_decoder(), ac_seek_ms(), or ac_free_decoder() call for this decoder.
*/
FFMPEG_EXTERN const uint8_t *fmpg_decoder_buffer(fmpg_decoder *decoder);
/* Size of the current decoded PCM buffer in bytes. */
FFMPEG_EXTERN int fmpg_decoder_buffer_size(fmpg_decoder *decoder);
/*
* Approximate timecode of the current decoded block in seconds.
*
* This is based on packet timestamps. It is useful for progress indication,
* but exact sample counting should use ac_decoder_sample_position().
*/
FFMPEG_EXTERN double fmpg_decoder_timecode(fmpg_decoder *decoder);
/*
* Number of output sample frames produced by the last decode/flush call.
*
* A sample frame contains one sample for each channel. For stereo S32, one
* sample frame is 8 bytes.
*/
FFMPEG_EXTERN int64_t fmpg_decoder_last_samples(fmpg_decoder *decoder);
/*
* Running count of output sample frames produced since decoder creation or
* the most recent successful seek.
*/
FFMPEG_EXTERN int64_t fmpg_decoder_sample_position(fmpg_decoder *decoder);
#ifdef __cplusplus #ifdef __cplusplus
} }