#include #include #include "fmt/format.h" #include "miniaudio.h" extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavcodec/avcodec.h" } struct ffmini { AVFormatContext* format_context; AVCodecContext* codec_context; std::int64_t stream_index = -1; char const* path = "C:/Users/miku/Downloads/Porter Robinson - Trying to Feel Alive (Official Audio).webm"; std::string filename{}; AVStream* stream = nullptr; AVPacket *pkt = nullptr; AVFrame* frame = nullptr; std::size_t byte_read = 0; ffmini() { format_context = avformat_alloc_context(); if (!format_context) throw std::runtime_error("Failed to create AVFormatContext"); if (avformat_open_input(&format_context, path, nullptr, nullptr) != 0) throw std::runtime_error(fmt::format("Failed to open file {}\n", filename)); if (avformat_find_stream_info(format_context, nullptr) < 0) throw std::runtime_error("Error loading stream info"); AVCodec const* codec = nullptr; for (std::uint32_t i = 0; i < format_context->nb_streams; ++i) { auto* params = format_context->streams[i]->codecpar; codec = avcodec_find_decoder(params->codec_id); if (!codec) continue; if (params->codec_type == AVMEDIA_TYPE_AUDIO) { stream_index = static_cast(i); } } if (stream_index == -1) throw std::runtime_error(fmt::format("Couldn't find valid audio track inside file: {}", filename)); codec_context = avcodec_alloc_context3(codec); if (!codec_context) throw std::runtime_error("Couldn't create AVCodecContext"); auto* codec_params = format_context->streams[stream_index]->codecpar; if (avcodec_parameters_to_context(codec_context, codec_params) < 0) throw std::runtime_error("Couldn't initialize AVCodecContext"); if (avcodec_open2(codec_context, codec, nullptr) < 0) throw std::runtime_error("Couln't open codec"); stream = format_context->streams[stream_index]; frame = av_frame_alloc(); if (!frame) throw std::runtime_error("Couldn't allocate AVFrame"); pkt = av_packet_alloc(); if (!pkt) throw std::runtime_error("Couldn't allocate AVPacket"); std::filesystem::path file{path}; filename = file.filename().string(); // char buffer[256]{}; // av_get_channel_layout_string(buffer, 256, codec_params->channels, codec_params->channel_layout); fmt::print("File: {}\n", filename); fmt::print("Sample rate: {}\n", codec_params->sample_rate); fmt::print("Channels: {}\n", codec_params->channels); } ~ffmini() { avcodec_free_context(&codec_context); avformat_close_input(&format_context); avformat_free_context(format_context); av_frame_free(&frame); av_packet_free(&pkt); } auto decode() -> void { if (byte_read != 0) return; static auto make_error = [](int err) { static char str[AV_ERROR_MAX_STRING_SIZE]{}; return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, err); }; int ret = 0; while (av_read_frame(format_context, pkt) >= 0) { if (pkt->stream_index != stream_index) { av_packet_unref(pkt); continue; } ret = avcodec_send_packet(codec_context, pkt); if (ret < 0) { fmt::print(stderr, "Error submitting the packet to the decoder!\n"); return; } ret = avcodec_receive_frame(codec_context, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { av_packet_unref(pkt); fmt::print("error\n"); continue; } else if (ret < 0) { fmt::print(stderr, "Failed to decode packet: %s\n", make_error(ret)); break; } av_packet_unref(pkt); break; } auto const data_size = av_get_bytes_per_sample(codec_context->sample_fmt); if (data_size < 0) { fmt::print(stderr, "Failed to calculate data size\n"); } } }; auto callback([[maybe_unused]]ma_device* device, [[maybe_unused]]void* output, [[maybe_unused]]void const* input, [[maybe_unused]]ma_uint32 frame_count) -> void { auto* ptr = static_cast(device->pUserData); ptr->decode(); auto& start = ptr->byte_read; auto* frame = ptr->frame; auto* codec = ptr->codec_context; std::size_t read = 0; auto const data_size = av_get_bytes_per_sample(codec->sample_fmt); if (data_size < 0) { fmt::print(stderr, "Failed to calculate data size\n"); } auto* dst = static_cast(output); for (int i = start; i < frame->nb_samples; i++) { for (int ch = 0; ch < codec->channels; ch++) { std::memcpy(dst++, frame->data[ch] + data_size * i, data_size); } ++read; if (read >= frame_count) break; } start = start + read; if (int(start) >= frame->nb_samples) start = 0; } auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int { ffmini decoder{}; ma_device device; auto device_config = ma_device_config_init(ma_device_type_playback); device_config.playback.format = ma_format_f32; device_config.playback.channels = 2; device_config.sampleRate = decoder.stream->codecpar->sample_rate; device_config.dataCallback = callback; device_config.pUserData = &decoder; if (ma_device_init(nullptr, &device_config, &device) != MA_SUCCESS) { fmt::print(stderr, "Failed to open playback device\n"); return 1; } if (ma_device_start(&device) != MA_SUCCESS) { fmt::print(stderr, "Failed to start playback device.\n"); return 1; } fmt::print("Now playing: {}\n", decoder.filename); fmt::print("Press enter to quit..."); std::cin.get(); ma_device_uninit(&device); return 0; }