diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake index a3b5481..d781330 100644 --- a/cmake/FindFFmpeg.cmake +++ b/cmake/FindFFmpeg.cmake @@ -1,3 +1,18 @@ +cmake_minimum_required(VERSION 3.21) + +if (NOT FFMPEG_FIND_COMPONENTS) + set(FFMPEG_FIND_COMPONENTS + AVCODEC + AVDEVICE + AVFILTER + AVFORMAT + AVUTIL + POSTPROC + SWRESAMPLE + SWSCALE + ) +endif() + find_path(FFMPEG_INCLUDE_DIR NAMES libavcodec @@ -10,36 +25,14 @@ find_path(FFMPEG_INCLUDE_DIR libswscale ) -find_library(FFMPEG_AVCODEC_LIBRARY avcodec) -find_library(FFMPEG_AVDEVICE_LIBRARY avdevice) -find_library(FFMPEG_AVFILTER_LIBRARY avfilter) -find_library(FFMPEG_AVFORMAT_LIBRARY avformat) -find_library(FFMPEG_AVUTIL_LIBRARY avutil) -find_library(FFMPEG_POSTPROC_LIBRARY postproc) -find_library(FFMPEG_SWRESAMPLE_LIBRARY swresample) -find_library(FFMPEG_SWSCALE_LIBRARY swscale) +foreach (component ${FFMPEG_FIND_COMPONENTS}) + string(TOLOWER ${component} library) + find_library(FFMPEG_${component}_LIBRARY ${library}) +endforeach() -#find_library(FFMPEG_AVCODEC_LIBRARY NAMES "avcodec.lib" "libavcodec.a") -#find_library(FFMPEG_AVDEVICE_LIBRARY NAMES "avdevice.lib" "libavdevice.a") -#find_library(FFMPEG_AVFILTER_LIBRARY NAMES "avfilter.lib" "libavfilter.a") -#find_library(FFMPEG_AVFORMAT_LIBRARY NAMES "avformat.lib" "libavformat.a") -#find_library(FFMPEG_AVUTIL_LIBRARY NAMES "avutil.lib" "libavutil.a") -#find_library(FFMPEG_POSTPROC_LIBRARY NAMES "postproc.lib" "libpostproc.a") -#find_library(FFMPEG_SWRESAMPLE_LIBRARY NAMES "swresample.lib" "libswresample.a") -#find_library(FFMPEG_SWSCALE_LIBRARY NAMES "swscale.lib" "libswscale.a") - -if (FFMPEG_INCLUDE_DIR AND - FFMPEG_AVCODEC_LIBRARY AND - FFMPEG_AVDEVICE_LIBRARY AND - FFMPEG_AVFILTER_LIBRARY AND - FFMPEG_AVFORMAT_LIBRARY AND - FFMPEG_AVUTIL_LIBRARY AND - FFMPEG_POSTPROC_LIBRARY AND - FFMPEG_SWRESAMPLE_LIBRARY AND - FFMPEG_SWSCALE_LIBRARY) - set(FFMPEG_FOUND TRUE) -else() - set(FFMPEG_FOUND FALSE) +if (WIN32) + #set(bin_path "${FFMPEG_INCLUDE_DIR}/../bin") + # TODO: Copy DLLs endif() include(FindPackageHandleStandardArgs) diff --git a/ffmini.cpp b/ffmini.cpp index 1374fee..fe8e1ad 100644 --- a/ffmini.cpp +++ b/ffmini.cpp @@ -1,3 +1,6 @@ +#include +#include + #include "fmt/format.h" #include "miniaudio.h" @@ -7,50 +10,157 @@ extern "C" { #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 { - AVFormatContext* format_context = avformat_alloc_context(); - if (!format_context) { - fmt::print(stderr, "Failed to create AVFormatContext\n"); - return 1; - } - - auto const filename = "C:/Users/miku/Downloads/Porter Robinson - Trying to Feel Alive (Official Audio).webm"; - if (avformat_open_input(&format_context, filename, nullptr, nullptr) != 0) { - fmt::print(stderr, "Failed to open file {}\n", filename); - return 1; - } - - if (avformat_find_stream_info(format_context, nullptr) < 0) { - fmt::print(stderr, "Error loading stream info\n"); - return 1; - } - - std::int64_t stream_index = -1; - 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) { - fmt::print(stderr, "Couldn't find valid audio track inside file: {}\n", filename); - return 1; - } + 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 = 48000; + device_config.sampleRate = decoder.stream->codecpar->sample_rate; device_config.dataCallback = callback; - device_config.pUserData = nullptr; + device_config.pUserData = &decoder; if (ma_device_init(nullptr, &device_config, &device) != MA_SUCCESS) { fmt::print(stderr, "Failed to open playback device\n"); @@ -62,9 +172,11 @@ auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int { return 1; } + fmt::print("Now playing: {}\n", decoder.filename); + fmt::print("Press enter to quit..."); + std::cin.get(); + ma_device_uninit(&device); - avformat_close_input(&format_context); - avformat_free_context(format_context); return 0; }