diff --git a/.gitignore b/.gitignore index 0162613..bbb7395 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ build lib/linux-x86_64 +build-ffmpeg /ffmpeg-audio/.qtcreator diff --git a/Makefile b/Makefile index 34b5dce..9e178db 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,27 @@ +SUBDIR := $(shell racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))") all: mkdir -p build cmake -S ao-play-async -B build (cd build; make) + cmake -S ffmpeg-audio -B build-ffmpeg + (cd build-ffmpeg; make) install: all - SUBDIR=`racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))"`; \ - mkdir -p lib/$$SUBDIR - SUBDIR=`racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))"`; \ - FILES=`ls build/*.so` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$$SUBDIR; fi - SUBDIR=`racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))"`; \ - FILES=`ls build/*.dll` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$$SUBDIR; fi + mkdir -p lib/$(SUBDIR) + FILES=`ls build/*.so build-ffmpeg/*.so` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$(SUBDIR); fi + FILES=`ls build/*.dll build-ffmpeg/*.dll` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$(SUBDIR); fi test: install cp lib/linux-x86_64/*.so ~/.local/share/racket/racket-sound-lib/linux-x86_64 zip: install - SUBDIR=`racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))"`; \ - (cd lib; zip -y -r -9 $$SUBDIR.zip $$SUBDIR) - SUBDIR=`racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))"`; \ - (cd lib; rm -rf $$SUBDIR) + (cd lib; zip -y -r -9 $(SUBDIR).zip $(SUBDIR)) + (cd lib; rm -rf $(SUBDIR)) clean: rm -rf build + rm -rf build-ffmpeg diff --git a/ao-play-async/ao_playasync.c b/ao-play-async/ao_playasync.c index b55e36c..9e1b113 100644 --- a/ao-play-async/ao_playasync.c +++ b/ao-play-async/ao_playasync.c @@ -100,7 +100,7 @@ typedef struct _queue_ { struct _queue_ *prev; } Queue_t; -typedef int(*ao_play_func_t)(void *, char *, uint32_t); +struct AO_Handle; typedef struct { Queue_t *play_queue; @@ -109,6 +109,7 @@ typedef struct { int paused; ao_device *ao_device; + int requested_bits_per_sample; int dev_bits_per_sample; int dev_endianess; int dev_channels; @@ -348,9 +349,131 @@ static void init_ao() if (!init) { ao_initialize(); atexit(at_exit_shutdown_ao); + init = 1; } } + +static ao_device *try_open_device(int bits, int rate, int channels, int byte_format, + const char *wav_file_output, + int *opened_bits) +{ + int candidates[3]; + int n = 0; + + candidates[n++] = bits; + + if (bits > 24) candidates[n++] = 24; + if (bits > 16) candidates[n++] = 16; + + for (int i = 0; i < n; i++) { + ao_sample_format fmt; + fmt.bits = candidates[i]; + fmt.rate = rate; + fmt.byte_format = byte_format; + fmt.channels = channels; + fmt.matrix = NULL; + + ao_device *dev; + + if (wav_file_output != NULL) { + int driver_id = ao_driver_id("wav"); + dev = ao_open_file(driver_id, wav_file_output, 1, &fmt, NULL); + } else { + dev = ao_open_live(ao_default_driver_id(), &fmt, NULL); + } + + if (dev != NULL) { + *opened_bits = candidates[i]; + return dev; + } + } + + return NULL; +} + + +static inline int32_t convert_bits(int32_t sample, int req_bits, int out_bits) +{ + if (req_bits > out_bits) { + sample >>= (req_bits - out_bits); + } else if (req_bits < out_bits) { + sample <<= (out_bits - req_bits); + } + return sample; +} + +static inline int32_t read_sample(unsigned char *mem, int req_bytes, int little_endian) +{ + uint32_t v = 0; + + for (int i = 0; i < req_bytes; i++) { + int idx = little_endian ? i : (req_bytes - i - 1); + v |= ((uint32_t) mem[idx]) << (8 * i); + } + + if (req_bytes < 4) { + int bits = req_bytes * 8; + uint32_t sign = 1u << (bits - 1); + v = (v ^ sign) - sign; + } + + return (int32_t) v; +} + +static inline void store_sample(unsigned char *dst, int32_t sample, int out_bytes, int is_little_endian) +{ + for (int i = 0; i < out_bytes; i++) { + int idx = is_little_endian ? i : (out_bytes - i - 1); + dst[idx] = sample & 0xff; + sample >>= 8; + } +} + +static void *convert_req_to_real(AO_Handle *h, void *mem, int mem_size, BufferInfo_t *info, int *out_size) +{ + int endianess = h->dev_endianess; + int little_endian = (endianess == AO_FMT_LITTLE); + if (!little_endian && endianess == AO_FMT_NATIVE) little_endian = littleEndian(); + + int requested_bits = h->requested_bits_per_sample; + int output_bits = h->dev_bits_per_sample; + + int req_bytes = (requested_bits / 8); + int out_bytes = (output_bits / 8); + + int samples = (mem_size / req_bytes); + + unsigned char *buf_out = (unsigned char *) malloc(samples * out_bytes); + if (buf_out == NULL) { + fprintf(stderr, "Allocation of output buffer of %d samples of %d bits gives NULL", samples, output_bits); + *out_size = 0; + return NULL; + } + + if (requested_bits == output_bits) { + *out_size = mem_size; + memcpy(buf_out, mem, mem_size); + } else { + // convert samples. + + int sample; + unsigned char *p_in = (unsigned char *) mem; + unsigned char *p_out = buf_out; + + for(sample = 0; sample < samples; sample++, p_in += req_bytes, p_out += out_bytes) { + register int32_t smpl; + smpl = read_sample(p_in, req_bytes, little_endian); + int32_t out_smpl = convert_bits(smpl, requested_bits, output_bits); + store_sample(p_out, out_smpl, out_bytes, little_endian); + } + + *out_size = samples * out_bytes; + } + + return buf_out; +} + void *ao_create_async(int bits, int rate, int channels, int byte_format, const char *wav_file_output) { init_ao(); @@ -364,21 +487,18 @@ void *ao_create_async(int bits, int rate, int channels, int byte_format, const c fmt.channels = channels; fmt.matrix = NULL; - ao_device *dev; - if (wav_file_output != NULL) { - int driver_id = ao_driver_id("wav"); - dev = ao_open_file(driver_id, wav_file_output, 1, &fmt, NULL); - } else { - dev = ao_open_live(ao_default_driver_id(), &fmt, NULL); - } + int opened_bits = 0; + ao_device *dev = try_open_device(bits, rate, channels, byte_format, wav_file_output, &opened_bits); if (dev == NULL) { fprintf(stderr, "Cannot open ao-device, error code = %d\n", errno); + free(handle); return NULL; } handle->ao_device = dev; - handle->dev_bits_per_sample = bits; + handle->requested_bits_per_sample = bits; + handle->dev_bits_per_sample = opened_bits; handle->dev_channels = channels; handle->dev_rate = rate; handle->dev_endianess = byte_format; @@ -563,12 +683,23 @@ void ao_play_async(void *ao_handle, int music_id, double at_second, double music case flac: { int store_size = 0; void *store_mem = convertFlac(mem, buf_size, &info, &store_size); - q = new_elem(PLAY, music_id, at_second, music_duration, store_size, store_mem); + + int ao_size = 0; + void *ao_mem = convert_req_to_real(ao_handle, store_mem, store_size, &info, &ao_size); + + q = new_elem(PLAY, music_id, at_second, music_duration, ao_size, ao_mem); + free(store_mem); + free(ao_mem); } break; case ao: { - q = new_elem(PLAY, music_id, at_second, music_duration, buf_size, mem); + int ao_size = 0; + void *ao_mem = convert_req_to_real(ao_handle, mem, buf_size, &info, &ao_size); + + q = new_elem(PLAY, music_id, at_second, music_duration, ao_size, ao_mem); + + free(ao_mem); } break; case mpg123: { @@ -680,3 +811,9 @@ void ao_pause_async(void *ao_handle, int paused) + +int ao_real_output_bits_async(void *handle) +{ + AO_Handle *h = (AO_Handle *) handle; + return h->dev_bits_per_sample; +} diff --git a/ao-play-async/ao_playasync.h b/ao-play-async/ao_playasync.h index 022fbdd..08c79d5 100644 --- a/ao-play-async/ao_playasync.h +++ b/ao-play-async/ao_playasync.h @@ -44,5 +44,6 @@ AOPLAYASYNC_EXPORT void ao_set_volume_async(void *ao_handle, double percentage); AOPLAYASYNC_EXPORT double ao_volume_async(void *ao_handle); AOPLAYASYNC_EXPORT int ao_bufsize_async(void *handle); +AOPLAYASYNC_EXPORT int ao_real_output_bits_async(void *handle); #endif // AO_PLAYASYNC_H diff --git a/ffmpeg-audio/CMakeLists.txt b/ffmpeg-audio/CMakeLists.txt index 7080a2a..229f987 100644 --- a/ffmpeg-audio/CMakeLists.txt +++ b/ffmpeg-audio/CMakeLists.txt @@ -21,6 +21,8 @@ if(WIN32) target_link_directories(ffmpeg_audio PRIVATE ${FFMPEG_LIB}) set(FFMPEG_LIBRARIES avcodec.lib avformat.lib swresample.lib avutil.lib avdevice.lib avfilter.lib swscale.lib) target_link_directories(demo_ffmpeg_audio PRIVATE ${FFMPEG_LIB}) +else() + set(FFMPEG_LIBRARIES avcodec avformat swresample avutil avdevice avfilter swscale) endif() target_link_libraries(ffmpeg_audio PRIVATE ${FFMPEG_LIBRARIES}) diff --git a/ffmpeg-audio/demo_ffmpeg_audio.c b/ffmpeg-audio/demo_ffmpeg_audio.c index 4ecbe46..e1bf417 100644 --- a/ffmpeg-audio/demo_ffmpeg_audio.c +++ b/ffmpeg-audio/demo_ffmpeg_audio.c @@ -4,6 +4,13 @@ #include #include +#include +#include +#include +#include +#include +#include + #ifdef WIN32 #define fprintf fprintf_s #endif @@ -97,6 +104,11 @@ int main(int argc, char **argv) infile = argv[1]; outfile = argv[2]; + fprintf(stderr, "ffmpeg compiled version: %s\n", fmpg_ffmpeg_version()); + fprintf(stderr, "ffmpeg runtime version avformat: %d - %s\n", avformat_version(), fmpg_int_version2string(avformat_version())); + + //fprintf(stderr, "ffmpeg runtme config string: %s\n", avformat_configuration()); + fmpg = fmpg_init(); if (!fmpg) { fprintf(stderr, "fmpg_init failed\n"); diff --git a/ffmpeg-audio/ffmpeg_audio.cpp b/ffmpeg-audio/ffmpeg_audio.cpp index c4fe23e..589f9ab 100644 --- a/ffmpeg-audio/ffmpeg_audio.cpp +++ b/ffmpeg-audio/ffmpeg_audio.cpp @@ -31,6 +31,21 @@ extern "C" { #include } +#define MSG0(type, msg) fprintf(stderr, type ":");fprintf(stderr, "%s", __FUNCTION__);fprintf(stderr, ": %s\n", msg) +#define MSG1(type, msg, a) fprintf(stderr, type ":");fprintf(stderr, "%s", __FUNCTION__);fprintf(stderr, ": " msg "\n", a) +#define MSG2(type, msg, a, b) fprintf(stderr, type ":");fprintf(stderr, "%s", __FUNCTION__);fprintf(stderr, ": " msg "\n", a, b) +#define MSG3(type, msg, a, b, c) fprintf(stderr, type ":");fprintf(stderr, "%s", __FUNCTION__);fprintf(stderr, ": " msg "\n", a, b, c) + +#define INFO0(msg) MSG0("info", msg) +#define INFO1(msg, a) MSG1("info", msg, a) +#define INFO2(msg, a, b) MSG2("info", msg, a, b) +#define INFO3(msg, a, b, c) MSG3("info", msg, a, b, c) + +#define ERROR0(msg) MSG0("error", msg) +#define ERROR1(msg, a) MSG1("error", msg, a) +#define ERROR2(msg, a, b) MSG2("error", msg, a, b) +#define ERROR3(msg, a, b, c) MSG3("error", msg, a, b, c) + static constexpr int FMPG_OUTPUT_BITS = 32; static constexpr int FMPG_OUTPUT_BYTES = 4; static constexpr AVSampleFormat FMPG_OUTPUT_FMT = AV_SAMPLE_FMT_S32; @@ -150,8 +165,11 @@ static const char *string_c_str(const std::string &s) } static std::string get_metadata_string(const AVFormatContext *ctx, - const char *key) + const char *key, bool &has_metadata) { + if (ctx->metadata == nullptr) { has_metadata = false; return ""; } + + has_metadata = true; const AVDictionaryEntry *entry = av_dict_get(ctx->metadata, key, nullptr, @@ -160,8 +178,11 @@ static std::string get_metadata_string(const AVFormatContext *ctx, : std::string(); } -static int get_metadata_int(const AVFormatContext *ctx, const char *key) +static int get_metadata_int(const AVFormatContext *ctx, const char *key, bool &has_metadata) { + if (ctx->metadata == nullptr) { has_metadata = false; return -1; } + has_metadata = true; + const AVDictionaryEntry *entry = av_dict_get(ctx->metadata, key, nullptr, @@ -239,15 +260,17 @@ static void fill_file_metadata(fmpg_instance *self) { AVFormatContext *ctx = self->format_ctx; + bool has_metadata; + self->file_info.clear(); - self->file_info.title = get_metadata_string(ctx, "title"); - self->file_info.author = get_metadata_string(ctx, "artist"); - self->file_info.album = get_metadata_string(ctx, "album"); - self->file_info.genre = get_metadata_string(ctx, "genre"); - self->file_info.comment = get_metadata_string(ctx, "comment"); - self->file_info.copyright = get_metadata_string(ctx, "copyright"); - self->file_info.year = get_metadata_int(ctx, "year"); - self->file_info.track = get_metadata_int(ctx, "track"); + self->file_info.title = get_metadata_string(ctx, "title", has_metadata); + self->file_info.author = get_metadata_string(ctx, "artist", has_metadata); + self->file_info.album = get_metadata_string(ctx, "album", has_metadata); + self->file_info.genre = get_metadata_string(ctx, "genre", has_metadata); + self->file_info.comment = get_metadata_string(ctx, "comment", has_metadata); + self->file_info.copyright = get_metadata_string(ctx, "copyright", has_metadata); + self->file_info.year = get_metadata_int(ctx, "year", has_metadata); + self->file_info.track = get_metadata_int(ctx, "track", has_metadata); self->file_info.bitrate = ctx->bit_rate > 0 ? static_cast(ctx->bit_rate) : -1; @@ -360,8 +383,44 @@ static bool init_decoder(fmpg_instance *self) return init_codec_context(self) && init_resampler(self); } +int fmpg_compatible_ffmpeg() +{ + int compiled_avformat_major = LIBAVFORMAT_VERSION_MAJOR; + int compiled_avcodec_major = LIBAVCODEC_VERSION_MAJOR; + int compiled_swresample_major = LIBSWRESAMPLE_VERSION_MAJOR; + int compiled_avutil_major = LIBAVUTIL_VERSION_MAJOR; + + int current_avformat_major = AV_VERSION_MAJOR(avformat_version()); + int current_avcodec_major = AV_VERSION_MAJOR(avcodec_version()); + int current_swresample_major = AV_VERSION_MAJOR(swresample_version()); + int current_avutil_major = AV_VERSION_MAJOR(avutil_version()); + + auto comp = [](const char *lib, int cv, int rv) { + if (cv != rv) { + ERROR3("FFMPEG %s Major versions not equal. Compile time: %d, runtime: %d", lib, cv, rv); + } + }; + + comp("AVFormat", compiled_avformat_major, current_avformat_major); + comp("AVCodec", compiled_avcodec_major, current_avcodec_major); + comp("SWResample", compiled_swresample_major, current_swresample_major); + comp("AVUtil", compiled_avutil_major, current_avutil_major); + + int compatible = (compiled_avformat_major == current_avformat_major) && + (compiled_avcodec_major == current_avcodec_major) && + (compiled_swresample_major == current_swresample_major) && + (compiled_avutil_major == current_avutil_major); + + return compatible; +} + fmpg_instance *fmpg_init(void) { + if (!fmpg_compatible_ffmpeg()) { + ERROR0("Compiled major ffmpeg version ≃ runtime major version, not compatible."); + return nullptr; + } + try { return new fmpg_instance(); } catch (...) { @@ -380,14 +439,21 @@ int fmpg_open_file(fmpg_instance *instance, const char *filename) return 0; } - if (avformat_open_input(&instance->format_ctx, - filename, - nullptr, - nullptr) < 0) { + if (instance->format_ctx != nullptr) { + return 0; + } + + int r = avformat_open_input(&instance->format_ctx, + filename, + nullptr, + nullptr); + if (r < 0) { fmpg_close(instance); return 0; } + fprintf(stderr, "metadata: %p", instance->format_ctx->metadata); + if (avformat_find_stream_info(instance->format_ctx, nullptr) < 0) { fmpg_close(instance); return 0; @@ -923,3 +989,35 @@ double fmpg_timecode(fmpg_instance *instance) { return instance ? instance->decoder.timecode : 0.0; } + +const char *fmpg_ffmpeg_version() +{ + static char *version = nullptr; + + if (version == nullptr) { + version = static_cast(malloc(1024)); + } + sprintf(version, "avformat: %u.%u.%u (%d), avcodec: %u.%u.%u (%d), swresample: %u.%u.%u (%d), avutil: %u.%u.%u (%d)", + LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO, LIBAVFORMAT_VERSION_INT, + LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO, LIBAVCODEC_VERSION_INT, + LIBSWRESAMPLE_VERSION_MAJOR, LIBSWRESAMPLE_VERSION_MINOR, LIBSWRESAMPLE_VERSION_MICRO, LIBSWRESAMPLE_VERSION_INT, + LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO, LIBAVUTIL_VERSION_INT + ); + return version; +} + +const char *fmpg_int_version2string(int ver) +{ + static char *version = nullptr; + + if (version == nullptr) { + version = static_cast(malloc(1024)); + } + + int major = AV_VERSION_MAJOR(ver); + int minor = AV_VERSION_MINOR(ver); + int micro = AV_VERSION_MICRO(ver); + sprintf(version, "%u.%u.%u", major, minor, micro); + + return version; +} diff --git a/ffmpeg-audio/ffmpeg_audio.h b/ffmpeg-audio/ffmpeg_audio.h index cdfdcb2..7dbd4aa 100644 --- a/ffmpeg-audio/ffmpeg_audio.h +++ b/ffmpeg-audio/ffmpeg_audio.h @@ -150,6 +150,10 @@ FFMPEG_EXTERN int64_t fmpg_sample_position(fmpg_instance *instance); /* Approximate start time of the current decoded block in seconds. */ FFMPEG_EXTERN double fmpg_timecode(fmpg_instance *instance); +FFMPEG_EXTERN const char *fmpg_ffmpeg_version(); +FFMPEG_EXTERN const char *fmpg_int_version2string(int ver); +FFMPEG_EXTERN int fmpg_compatible_ffmpeg(); + #ifdef __cplusplus } #endif diff --git a/lib/linux-x86_64.zip b/lib/linux-x86_64.zip index e5f0c5a..db63c6d 100644 Binary files a/lib/linux-x86_64.zip and b/lib/linux-x86_64.zip differ