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
+86 -61
View File
@@ -3,7 +3,6 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#define fprintf fprintf_s
@@ -26,18 +25,18 @@ static int write_wav_header(FILE *f,
int channels,
int bits_per_sample,
uint32_t data_size) {
uint32_t byte_rate =
const uint32_t byte_rate =
(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);
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("fmt ", 1, 4, f);
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_u32_le(f, (uint32_t)sample_rate);
write_u32_le(f, byte_rate);
@@ -66,16 +65,37 @@ static int rewrite_wav_header(FILE *f,
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) {
const char *infile;
const char *outfile;
fmpg_instance * ac = NULL;
fmpg_decoder * dec = NULL;
fmpg_instance *ac = NULL;
fmpg_decoder *dec = NULL;
FILE *out = NULL;
int stream;
fmpg_audio_info info;
int sample_rate;
int channels;
int bits_per_sample;
int64_t duration_ms;
int64_t duration_samples;
uint64_t total_written = 0;
if (argc != 3) {
@@ -86,44 +106,42 @@ int main(int argc, char **argv) {
infile = argv[1];
outfile = argv[2];
ac = ac_init();
ac = fmpg_init();
if (!ac) {
fprintf(stderr, "ac_init failed\n");
return 1;
}
if (!ac_open_file(ac, infile)) {
if (!fmpg_open_file(ac, infile)) {
fprintf(stderr, "could not open input file: %s\n", infile);
ac_free(ac);
fmpg_free(ac);
return 1;
}
stream = ac_get_default_audio_stream(ac);
if (stream < 0) {
fprintf(stderr, "no audio stream found\n");
ac_free(ac);
sample_rate = fmpg_audio_sample_rate(ac);
channels = fmpg_audio_channels(ac);
bits_per_sample = fmpg_audio_bits_per_sample(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;
}
memset(&info, 0, sizeof(info));
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);
dec = fmpg_create_decoder(ac);
if (!dec) {
fprintf(stderr, "could not create decoder\n");
ac_free(ac);
fmpg_free(ac);
return 1;
}
out = fopen(outfile, "wb");
if (!out) {
fprintf(stderr, "could not open output file: %s\n", outfile);
ac_free_decoder(dec);
ac_free(ac);
fmpg_free_decoder(dec);
fmpg_free(ac);
return 1;
}
@@ -132,49 +150,50 @@ int main(int argc, char **argv) {
* Write a placeholder header first and patch it at the end.
*/
if (!write_wav_header(out,
info.sample_rate,
info.channels,
32,
sample_rate,
channels,
bits_per_sample,
0)) {
fprintf(stderr, "could not write WAV header\n");
fclose(out);
ac_free_decoder(dec);
ac_free(ac);
fmpg_free_decoder(dec);
fmpg_free(ac);
return 1;
}
for (;;) {
fmpg_package * pkg = ac_read_package(ac);
fmpg_package *pkg = fmpg_read_package(ac);
if (!pkg) {
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_read_package() now returns only packets from the internally
* selected audio stream. No stream_index test is needed anymore.
*/
if (fmpg_decode_package(pkg, dec)) {
if (!write_decoder_buffer(out, dec, &total_written)) {
fprintf(stderr, "could not write PCM data\n");
fmpg_free_package(pkg);
fclose(out);
fmpg_free_decoder(dec);
fmpg_free(ac);
return 1;
}
}
ac_free_package(pkg);
fmpg_free_package(pkg);
}
/*
* Drain delayed samples from the decoder and resampler.
*/
while (ac_flush_decoder(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;
/* 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,
info.sample_rate,
info.channels,
32,
sample_rate,
channels,
bits_per_sample,
(uint32_t)total_written)) {
fprintf(stderr, "could not rewrite WAV header\n");
}
fclose(out);
ac_free_decoder(dec);
ac_free(ac);
printf("wrote %s\n", outfile);
printf("sample rate: %d\n", info.sample_rate);
printf("channels: %d\n", info.channels);
printf("sample bits: %d\n", 32);
printf("title: %s\n", fmpg_file_title(ac));
printf("album: %s\n", fmpg_file_album(ac));
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",
(unsigned long long)total_written);
fmpg_free_decoder(dec);
fmpg_free(ac);
return 0;
}
File diff suppressed because it is too large Load Diff
+223 -71
View File
@@ -18,93 +18,245 @@ extern "C" {
#endif
/*
* Audio-only Acinerella API.
* Audio-only FFmpeg wrapper.
*
* The implementation may be C++, but this header is plain C-compatible.
* All public structs below are opaque handles, except ac_audio_info.
* The implementation is C++, but this header is plain C-compatible. All
* 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_decoder__ fmpg_decoder;
typedef struct __fmpg_package__ fmpg_package;
typedef struct __fmpg_file_info__ fmpg_file_info;
typedef struct __fmpg_decoder__ fmpg_decoder;
typedef struct __fmpg_package__ fmpg_package;
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 */
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);
/* ------------------------------------------------------------------------- */
/* Lifecycle */
/* ------------------------------------------------------------------------- */
/*
* Decode one compressed packet.
* Create an empty decoder instance.
*
* Returns 1 if PCM data was produced, 0 otherwise.
*
* Output format:
* signed 32-bit integer PCM
* interleaved
* native endian
*
* Example stereo layout:
* L0 R0 L1 R1 L2 R2 ...
* Return:
* instance pointer, or NULL on allocation failure.
*/
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.
*
* Call this repeatedly after ac_read_package() returns NULL,
* until it returns 0.
* Call this repeatedly after ac_read_package() returns NULL, until this
* 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 */
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);
/* ------------------------------------------------------------------------- */
/* Decoder output */
/* ------------------------------------------------------------------------- */
/*
* 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
}