Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Could You provide example of decoding audio file to buffer compatible with SFML:: #146

Open
baziorek opened this issue Jan 8, 2025 · 4 comments
Assignees

Comments

@baziorek
Copy link

baziorek commented Jan 8, 2025

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.

@h4tr3d
Copy link
Owner

h4tr3d commented Jan 11, 2025

Hi, will try to prepare in near two days.

Installing avcpp via vcpkg is a good case :)

@h4tr3d
Copy link
Owner

h4tr3d commented Jan 11, 2025

Look into attached sample: sfml-audio-pcm16.zip

@baziorek
Copy link
Author

baziorek commented Jan 11, 2025

Look into attached sample: sfml-audio-pcm16.zip

Thanks a lot for the example!
It works, only few things I've noticed to consider before adding to main branch:

  1. adding header #include <optional> will make code more portable, because when I used g++ directly there were compilation error
#if 0
    AudioFileData audioData = decodeAudioWithFFmpeg( filePath );
#else
    AudioFileData audioData = decodeAudioWithAvcpp( filePath );
#endif

BTW. About vcpkg: when we install:

./vcpkg/vcpkg install avcpp
Then it is installing * ffmpeg[avcodec,avdevice,avfilter,avformat,core,gpl,postproc,swresample,swscale]:[email protected]#2, and one of them is ffmpeg[gpl]. So avcpp when installing with VCPKG is having licence GPL (as I understand licenses).
So I'm suggesting to have more options of avcpp in vcpkg eg. avcpp[with-gpl] and avcpp[no-gpl]


PS: I'm creating article for site cpp0x.pl (article in Polish) where I'm trying to find IMO best way how to play music from C++ for programmers who don't know much about playing musics, codecs, etc. (article mostly for students). Can I paste Your code in the article?

@h4tr3d
Copy link
Owner

h4tr3d commented Jan 12, 2025

So I'm suggesting to have more options of avcpp in vcpkg eg. avcpp[with-gpl] and avcpp[no-gpl]

vcpkg support was not introduced by me. I have no time to dive deeply into details of the vcpkg work and ports Features supports (and its transitivity).

Can I paste Your code in the article?

Sure! I suggest only remove debug output just to reduce lines of code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants
@h4tr3d @baziorek and others