Successfully decode with ffmpeg and play audio with miniaudio
This commit is contained in:
@@ -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
|
find_path(FFMPEG_INCLUDE_DIR
|
||||||
NAMES
|
NAMES
|
||||||
libavcodec
|
libavcodec
|
||||||
@@ -10,36 +25,14 @@ find_path(FFMPEG_INCLUDE_DIR
|
|||||||
libswscale
|
libswscale
|
||||||
)
|
)
|
||||||
|
|
||||||
find_library(FFMPEG_AVCODEC_LIBRARY avcodec)
|
foreach (component ${FFMPEG_FIND_COMPONENTS})
|
||||||
find_library(FFMPEG_AVDEVICE_LIBRARY avdevice)
|
string(TOLOWER ${component} library)
|
||||||
find_library(FFMPEG_AVFILTER_LIBRARY avfilter)
|
find_library(FFMPEG_${component}_LIBRARY ${library})
|
||||||
find_library(FFMPEG_AVFORMAT_LIBRARY avformat)
|
endforeach()
|
||||||
find_library(FFMPEG_AVUTIL_LIBRARY avutil)
|
|
||||||
find_library(FFMPEG_POSTPROC_LIBRARY postproc)
|
|
||||||
find_library(FFMPEG_SWRESAMPLE_LIBRARY swresample)
|
|
||||||
find_library(FFMPEG_SWSCALE_LIBRARY swscale)
|
|
||||||
|
|
||||||
#find_library(FFMPEG_AVCODEC_LIBRARY NAMES "avcodec.lib" "libavcodec.a")
|
if (WIN32)
|
||||||
#find_library(FFMPEG_AVDEVICE_LIBRARY NAMES "avdevice.lib" "libavdevice.a")
|
#set(bin_path "${FFMPEG_INCLUDE_DIR}/../bin")
|
||||||
#find_library(FFMPEG_AVFILTER_LIBRARY NAMES "avfilter.lib" "libavfilter.a")
|
# TODO: Copy DLLs
|
||||||
#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)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
include(FindPackageHandleStandardArgs)
|
||||||
|
|||||||
184
ffmini.cpp
184
ffmini.cpp
@@ -1,3 +1,6 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "miniaudio.h"
|
#include "miniaudio.h"
|
||||||
|
|
||||||
@@ -7,50 +10,157 @@ extern "C" {
|
|||||||
#include "libavcodec/avcodec.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<std::int64_t>(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 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<ffmini*>(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<float*>(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 {
|
auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int {
|
||||||
AVFormatContext* format_context = avformat_alloc_context();
|
ffmini decoder{};
|
||||||
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<std::int64_t>(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream_index == -1) {
|
|
||||||
fmt::print(stderr, "Couldn't find valid audio track inside file: {}\n", filename);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ma_device device;
|
ma_device device;
|
||||||
auto device_config = ma_device_config_init(ma_device_type_playback);
|
auto device_config = ma_device_config_init(ma_device_type_playback);
|
||||||
device_config.playback.format = ma_format_f32;
|
device_config.playback.format = ma_format_f32;
|
||||||
device_config.playback.channels = 2;
|
device_config.playback.channels = 2;
|
||||||
device_config.sampleRate = 48000;
|
device_config.sampleRate = decoder.stream->codecpar->sample_rate;
|
||||||
device_config.dataCallback = callback;
|
device_config.dataCallback = callback;
|
||||||
device_config.pUserData = nullptr;
|
device_config.pUserData = &decoder;
|
||||||
|
|
||||||
if (ma_device_init(nullptr, &device_config, &device) != MA_SUCCESS) {
|
if (ma_device_init(nullptr, &device_config, &device) != MA_SUCCESS) {
|
||||||
fmt::print(stderr, "Failed to open playback device\n");
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt::print("Now playing: {}\n", decoder.filename);
|
||||||
|
fmt::print("Press enter to quit...");
|
||||||
|
std::cin.get();
|
||||||
|
|
||||||
ma_device_uninit(&device);
|
ma_device_uninit(&device);
|
||||||
avformat_close_input(&format_context);
|
|
||||||
avformat_free_context(format_context);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user