Description
I have example how to do something with FFMPEG and SFML - how to play almost any audio/video. I have code which is decoding audio/video file with ffmpeg to array then the code is sending the array to SFML which is playing the music.
I want to show to students really simple code, which does not require knowledge about audio in programming, that is why I want to change my code from using ffmpeg directly to use AVcpp.
I found example: https://github.com/h4tr3d/avcpp/blob/master/example/api2-samples/api2-decode-audio.cpp but I can't adapt its to work with my code.
Here is code which is playing music with ffmpeg + SFML:
#include <iostream>
#include <vector>
#include <thread>
#include <chrono>
#include <SFML/Audio.hpp>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libavutil/mem.h>
}
struct AudioFileData
{
int channels;
int sampleRate;
std::vector < sf::Int16 > pcmAudioBuffer;
bool successReading;
};
AudioFileData decodeAudioWithFFmpeg( const std::string & filePath );
void playSound( const AudioFileData & audioFileData );
int main( int argc, char * * argv ) {
if( argc < 2 ) {
std::cerr << "Usage: " << argv[ 0 ] << " <audio file>" << std::endl;
return 1;
}
const std::string filePath = argv[ 1 ];
AudioFileData audioData = decodeAudioWithFFmpeg( filePath );
if( audioData.pcmAudioBuffer.empty() )
std::cerr << "Failed to decode or play audio from file " << filePath << std::endl;
else
playSound( audioData );
}
AudioFileData decodeAudioWithFFmpeg( const std::string & filePath ) {
avformat_network_init();
AVFormatContext * formatContext = nullptr;
if( avformat_open_input( & formatContext, filePath.c_str(), nullptr, nullptr ) != 0 ) {
std::cerr << "Error: Could not open audio file with FFmpeg: " << filePath << std::endl;
return { };
}
if( avformat_find_stream_info( formatContext, nullptr ) < 0 ) {
std::cerr << "Error: Could not retrieve stream info." << std::endl;
avformat_close_input( & formatContext );
return { };
}
const AVCodec * codec = nullptr;
AVCodecContext * codecContext = nullptr;
int streamIndex = - 1;
for( unsigned int i = 0; i < formatContext->nb_streams; ++i ) {
if( formatContext->streams[ i ]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO ) {
codec = avcodec_find_decoder( formatContext->streams[ i ]->codecpar->codec_id );
if( !codec ) {
std::cerr << "Error: Unsupported codec for audio stream." << std::endl;
avformat_close_input( & formatContext );
return { };
}
codecContext = avcodec_alloc_context3( codec );
avcodec_parameters_to_context( codecContext, formatContext->streams[ i ]->codecpar );
if( avcodec_open2( codecContext, codec, nullptr ) < 0 ) {
std::cerr << "Error: Could not open codec." << std::endl;
avcodec_free_context( & codecContext );
avformat_close_input( & formatContext );
return { };
}
streamIndex = i;
break;
}
}
if( streamIndex == - 1 ) {
std::cerr << "Error: No audio stream found in file." << std::endl;
avcodec_free_context( & codecContext );
avformat_close_input( & formatContext );
return { };
}
SwrContext * swrContext = swr_alloc();
if( !swrContext ) {
std::cerr << "Error: Could not allocate SwrContext." << std::endl;
avcodec_free_context( & codecContext );
avformat_close_input( & formatContext );
return { };
}
swr_alloc_set_opts2( & swrContext,
& codecContext->ch_layout, AV_SAMPLE_FMT_S16, codecContext->sample_rate,
& codecContext->ch_layout, codecContext->sample_fmt, codecContext->sample_rate,
0, nullptr );
if( swr_init( swrContext ) < 0 ) {
std::cerr << "Error: Could not initialize SwrContext." << std::endl;
swr_free( & swrContext );
avcodec_free_context( & codecContext );
avformat_close_input( & formatContext );
return { };
}
AVPacket * packet = av_packet_alloc();
AVFrame * frame = av_frame_alloc();
AudioFileData audioFileData;
audioFileData.sampleRate = codecContext->sample_rate;
audioFileData.channels = codecContext->ch_layout.nb_channels;
while( av_read_frame( formatContext, packet ) >= 0 ) {
if( packet->stream_index == streamIndex ) {
if( avcodec_send_packet( codecContext, packet ) == 0 ) {
while( avcodec_receive_frame( codecContext, frame ) == 0 ) {
int bufferSize = av_samples_get_buffer_size( nullptr, audioFileData.channels, frame->nb_samples, AV_SAMPLE_FMT_S16, 1 );
std::vector < uint8_t > tempBuffer( bufferSize );
uint8_t * tempBufferPtr = tempBuffer.data();
swr_convert( swrContext, & tempBufferPtr, frame->nb_samples, frame->data, frame->nb_samples );
audioFileData.pcmAudioBuffer.insert( end( audioFileData.pcmAudioBuffer ),
( sf::Int16 * ) tempBuffer.data(),
( sf::Int16 * ) tempBuffer.data() + bufferSize / sizeof( sf::Int16 ) );
}
}
}
av_packet_unref( packet );
}
av_frame_free( & frame );
av_packet_free( & packet );
swr_free( & swrContext );
avcodec_free_context( & codecContext );
avformat_close_input( & formatContext );
return audioFileData;
}
void playSound( const AudioFileData & audioFileData ) {
sf::SoundBuffer soundBuffer;
if( !soundBuffer.loadFromSamples( audioFileData.pcmAudioBuffer.data(), audioFileData.pcmAudioBuffer.size(), audioFileData.channels, audioFileData.sampleRate ) ) {
std::cerr << "Error: Could not load buffer for playback" << std::endl;
return;
}
sf::Sound sound;
sound.setBuffer( soundBuffer );
sound.play();
std::cout << "Playing file, duration: " << soundBuffer.getDuration().asSeconds() << " seconds" << std::endl;
while( sound.getStatus() == sf::Sound::Playing ) {
std::this_thread::sleep_for( std::chrono::milliseconds( 500 ) );
}
std::cout << "Playback finished." << std::endl;
}
I know that SFML can play few audio formats, but I want to play various formats. Just SFML is IMO easiest code to play audio file:
#include <iostream>
#include <thread>
#include <chrono>
#include <SFML/Audio.hpp>
int main() {
const std::string filePath = "audio.wav";
sf::Music music;
if (!music.openFromFile(filePath)) {
std::cerr << "Error: Could not load file " << filePath << std::endl;
return 1;
}
std::cout << "Now playing: " << filePath << ", duration: " << music.getDuration().asSeconds() << " seconds" << std::endl;
music.play();
while (sf::Music::Playing == music.getStatus()) {
std::cout << "\rCurrent position: " << music.getPlayingOffset().asSeconds()
<< " / " << music.getDuration().asSeconds() << " seconds" << std::flush;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::cout << "\nPlayback finished." << std::endl;
}
Fastest way to install with vcpkg
git clone https://github.com/Microsoft/vcpkg.git --depth=1
./vcpkg/bootstrap-vcpkg.sh --disable-metrics
./vcpkg/vcpkg install 'sfml[audio]'
./vcpkg/vcpkg install 'ffmpeg[all]'
Then sample CMakeLists.txt:
cmake_minimum_required(VERSION 3.31)
project(AudioTerminal LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# binary1 (just SFML)
find_package(SFML COMPONENTS system window graphics audio CONFIG REQUIRED)
add_executable(SFML_AUDIO sfml1.cpp)
target_link_libraries(SFML_AUDIO PRIVATE sfml-system sfml-network sfml-graphics sfml-window sfml-audio)
# binary2 (SFML + FFMPEG)
find_package(PkgConfig REQUIRED)
pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET
libavformat
libavcodec
libswresample
libavutil
)
add_executable(SFML_FFMPEG sfml_ffmpeg.cpp)
target_include_directories(SFML_FFMPEG PRIVATE ${PROJECT_SOURCE_DIR} PkgConfig::FFMPEG)
target_link_libraries(SFML_FFMPEG PRIVATE PkgConfig::FFMPEG sfml-audio sfml-system)
As I know to install avcpp with vcpkg we need just:
vcpkg install avcpp
but probably You have better way to install the library:D.
BTW. Really good job with the library avcpp. I'd love to use its, but I need help.