使用FFmpeg编写音乐播放器

http://www.hyq.me/2013/07/10/play-with-ffmpeg/


本文介绍了用ffmpeg编写一个简单的音乐播放器, 适合初学者入门一下.

本文用FFmpeg进行音频解码,用PortAudio播放声音.

/// main.cpp

// 这里是ffmpeg的头文件, 如果是用c++, 必须加上extern "C", 否则可能导致链接时出错.
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
}

// 这里是PortAudio的头文件
#include <portaudio.h>
#include <assert.h>
#include <iostream>

struct AudioContext {
    AVCodecContext* codecContext;
    SwrContext* swrContext;
    ReSampleContext* resamplerContext;
};

static
void audio_copy(AudioContext *context,  AVFrame *dst, AVFrame* src)
{
    int nb_sample;
    int dst_buf_size;
    int out_channels;
    int bytes_per_sample = 0;

    dst->linesize[0] = src->linesize[0];
    *dst = *src;
    dst->data[0] = NULL;
    dst->type = 0;

    /* 备注: FFMIN(codecContext->channels, 2); 会有问题, 因为swr_alloc_set_opts的out_channel_layout参数. */
    out_channels = context->codecContext->channels;

    bytes_per_sample = av_get_bytes_per_sample(context->codecContext->sample_fmt);
    /* 备注: 由于 src->linesize[0] 可能是错误的, 所以计算得到的nb_sample会不正确, 直接使用src->nb_samples即可. */
    nb_sample = src->nb_samples;/* src->linesize[0] / codecContext->channels / bytes_per_sample; */
    bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
    dst_buf_size = nb_sample * bytes_per_sample * out_channels;
    dst->data[0] = (uint8_t*) av_malloc(dst_buf_size);
    assert(dst->data[0]);
    avcodec_fill_audio_frame(dst, out_channels, AV_SAMPLE_FMT_S16, dst->data[0], dst_buf_size, 0);

    /* 重采样到AV_SAMPLE_FMT_S16格式. */
    if (context->codecContext->sample_fmt != AV_SAMPLE_FMT_S16)
    {
        if (!context->swrContext)
        {
            uint64_t in_channel_layout = av_get_default_channel_layout(context->codecContext->channels);
            uint64_t out_channel_layout = av_get_default_channel_layout(out_channels);
            context->swrContext = swr_alloc_set_opts(NULL,
                out_channel_layout, AV_SAMPLE_FMT_S16, context->codecContext->sample_rate,
                in_channel_layout, context->codecContext->sample_fmt, context->codecContext->sample_rate,
                0, NULL);
            swr_init(context->swrContext);
        }

        if (context->swrContext)
        {
            int ret, out_count;
            out_count = dst_buf_size / out_channels / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
            ret = swr_convert(context->swrContext, dst->data, out_count, const_cast<const uint8_t**>(src->data), nb_sample);
            if (ret < 0)
                assert(0);
            src->linesize[0] = dst->linesize[0] = ret * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * out_channels;
            memcpy(src->data[0], dst->data[0], src->linesize[0]);
        }
    }

    /* 重采样到双声道. */
    if (context->codecContext->channels > 2)
    {
        if (!context->resamplerContext)
        {
            context->resamplerContext = av_audio_resample_init(
                    FFMIN(2, context->codecContext->channels),
                    context->codecContext->channels, context->codecContext->sample_rate,
                    context->codecContext->sample_rate, AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S16,
                    16, 10, 0, 0.f);
        }

        if (context->resamplerContext)
        {
            int samples = src->linesize[0] / (av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * context->codecContext->channels);
            dst->linesize[0] = audio_resample(context->resamplerContext,
                (short *) dst->data[0], (short *) src->data[0], samples) * 4;
        }
    }
    else
    {
        dst->linesize[0] = dst->linesize[0];
        memcpy(dst->data[0], src->data[0], dst->linesize[0]);
    }
}

int main(int argc, char* argv[]) 
{
    // 将要打开的音频文件(视频文件也可以支持).
    const char* filename = argc > 1 ? argv[1] : "1.mp3";

    // 初始化libavformat,并注册所有的模块
    av_register_all();

    // 这里一定要设置成NULL, 或者调用avformat_alloc_context分配内存, 否则可能崩溃.
    AVFormatContext *formatContext = NULL;

    // 打开输入文件.
    if( avformat_open_input(&formatContext, filename, NULL, NULL) < 0) {
        std::cerr << "cannot open file" << std::endl;
        return -1;
    }

    // 探测文件里面的音视频流信息.
    if( avformat_find_stream_info(formatContext, NULL) < 0) {
        std::cerr << "cannot find stream info" << std::endl;
        return -1;
    }

    // 输出来看看.
    av_dump_format(formatContext, 0, 0, 0);

    // 找到音频流的索引(如果是视频的话,可能存在多个流).
    int audioIndex;
    if((audioIndex = av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, 0, 0, NULL, 0)) < 0) {
        std::cerr << "cannot find audio stream" << std::endl;
        return -1;
    }

    AVCodecContext *codecContext = formatContext->streams[audioIndex]->codec;
    // 找到解码器.
    AVCodec *codec = avcodec_find_decoder(codecContext->codec_id);
    if(codec == NULL) {
        std::cerr << "cannot find decoder for " << codecContext->codec_name << std::endl;
    }

    // 打开解码器.
    if( avcodec_open2(codecContext, codec, NULL) < 0) {
        std::cerr << "cannot open decoder" << std::endl;
        return -1;
    }

    // AVPacket是解码前的数据, AVFrame是解码后的数据.
    AVPacket packet;
    AVFrame *frame = avcodec_alloc_frame();
    int got;
    AudioContext context;
    context.resamplerContext = NULL;
    context.swrContext = NULL;
    context.codecContext = codecContext;

    // 下面是初始化PortAudio, 用PortAudio的Blocking API比较简单.
    PaStream *stream;
    Pa_Initialize();
    Pa_OpenDefaultStream(&stream, 0, codecContext->channels, 
        paInt16, codecContext->sample_rate, 
        1024, NULL, NULL);
    Pa_StartStream(stream);
    codecContext->sample_fmt;
    int size = 4092;
    uint8_t* buffer = new uint8_t[size * 2];
    int channels = codecContext->channels;
    while(true) {
        // 从文件中读取一帧.
        if(av_read_frame(formatContext, &packet) < 0) {
            // 文件读完了.
            break;
        }

        // 解码.
        if( avcodec_decode_audio4(codecContext, frame, &got, &packet) < 0) {
            std::cerr << "cannot decode" << std::endl;
            // 偶尔会出错,一般都可以原谅的...
            // break;
        }

        // 解码出来了一帧
        if(got) {
            // 因为frame->data[0]表示的是左声道LLL....,frame->data[1]表示右声道RRR...
            // 而PortAudio要求的是LRLRLR....这样的数据排布, 所以这里用循环重新将数据复制到buffer中
            AVFrame *dst = avcodec_alloc_frame();
            audio_copy(&context, dst, frame);
            Pa_WriteStream(stream, reinterpret_cast<int16_t*>(dst->data[0]), dst->nb_samples);
        }

    }
    delete buffer;
    return 0; 
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: FFmpeg是一个开源的跨平台多媒体处理工具,可以用来编码、解码、转码和播放各种音频和视频文件。SDL(Simple DirectMedia Layer)是一个跨平台的多媒体库,可以用来处理音频、视频和输入设备。 FFmpeg和SDL可以结合使用实现一个简单的媒体播放器。首先,我们需要使用FFmpeg来解码音频和视频文件。通过FFmpeg的解码功能,我们可以将音频和视频数据解析出来,然后就可以利用SDL将它们播放出来。 在SDL中,我们可以创建一个音频流和一个视频流,并将解码后的音频和视频数据分别写入其中。SDL会负责将这些数据渲染到音频设备和视频窗口,从而实现播放效果。我们可以通过控制音频和视频流的缓冲区大小和时钟同步来实现音视频的同步播放。 另外,我们还可以利用FFmpeg的一些其他功能来提升播放器的性能和功能。例如,可以使用FFmpeg的过滤器功能来实现音频和视频的裁剪、旋转、缩放等操作。也可以利用FFmpeg的网络协议支持来播放网络上的音频和视频流。 总之,FFmpeg和SDL可以组成一个简单但功能强大的媒体播放器。通过使用FFmpeg的解码功能和SDL的渲染功能,我们可以实现音视频的解码和播放。而且,FFmpeg提供了许多其他的功能,比如过滤器、网络协议支持等,可以让我们的播放器更加灵活和强大。 ### 回答2: ffmpeg sdl播放器是一种基于ffmpeg和SDL库开发的音视频播放器。ffmpeg是一种开源的多媒体处理库,可以对音视频数据进行解码、编码、转码等操作。SDL是一种跨平台的多媒体开发库,可以实现多媒体的显示、音频播放、事件处理等功能。 使用ffmpeg sdl播放器可以实现对各种音视频格式的播放。首先,它可以将各种格式的音视频文件进行解码,将数据转换成可供显示和播放的格式。然后,通过SDL库可以将解码后的音频数据进行声音的播放,并将视频数据进行显示。 此外,ffmpeg sdl播放器还支持音视频的同步播放。它会根据视频帧的时间戳来计算音频数据的播放时间,从而实现音视频的同步播放。同时,它还能够处理音视频的各种事件,如播放暂停、快进、快退等。 ffmpeg sdl播放器还具有良好的扩展性和可定制性。开发者可以根据自己的需求,进行特定功能的定制和扩展,如添加字幕显示、视频特效等功能。 总的来说,ffmpeg sdl播放器是一个功能强大、灵活性高的音视频播放器。它可以支持各种音视频格式,实现音视频的同步播放,并且具有良好的扩展性。无论是在桌面应用还是移动应用中,ffmpeg sdl播放器都是一个理想的选择。 ### 回答3: ffmpeg sdl播放器是一款基于FFmpeg和SDL(Simple DirectMedia Layer)库开发的视频播放器。通过FFmpeg库,它可以解码各种视频格式,并且还支持音频解码功能。SDL库则提供了跨平台的图形、声音、事件处理等功能。 这款播放器具有以下特点和功能: 1. 跨平台性能:FFmpeg和SDL都是跨平台的库,因此该播放器可以在多个操作系统(如Windows、Mac OS、Linux等)上运行,并且具有良好的性能和稳定性。 2. 支持多种视频格式:FFmpeg库提供了广泛的视频格式支持,如AVI、MP4、MKV等,因此该播放器可以播放多种常见的视频文件。 3. 支持多种音频格式:除了视频,该播放器还支持音频解码,可以播放多种音频格式,如MP3、AAC、FLAC等。 4. 播放控制和界面:播放器提供基本的播放控制,如播放、暂停、快进、快退等功能,同时还有播放进度条和音量控制条,用户可以根据需求进行设置。 5. 良好的用户体验:播放器界面简洁易用,具有良好的用户体验,适合不同年龄段的用户使用。 总之,ffmpeg sdl播放器是一款功能强大且易于使用的视频播放器,它支持多种视频和音频格式,提供基本的播放控制和界面,可以在不同操作系统上运行。无论是观看电影、视频教程,还是听音乐,这款播放器都能满足用户的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值