FFMPEG-3.2.2 SDL-2.0.5(3)

本文介绍如何使用FFMPEG 3.2.2 和 SDL 2.0.5 在VS2015上创建一个简单的音频播放器。内容包括对应的教程链接以及参考的博客资源,强调了音频解码后的转换处理以适应SDL播放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Playing Sound

对应教程地址Tutorial 03: Playing Sound

参考博客:最简单的基于FFMPEG+SDL的音频播放器 ver2 (采用SDL2.0)

这段代码在VS2015上编译通过,文件格式是.c,不是.cpp。
这段代码跟官网上的有部分不同,是因为音频解码以后,需要转换,SDL才能播放出来

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>

#include <SDL.h>
#include <SDL_thread.h>

#include <stdio.h>
#include <string.h>
#include <assert.h>

#define  MAX_AUDIO_FRAME_SIZE 192000;
#define  SDL_AUDIO_BUFFER_SIZE 1024;

typedef struct
{
    AVPacketList *first_pkt, *last_pkt;
    int nb_packets;
    int size;
    SDL_mutex *mutext;
    SDL_cond *cond;
}PacketQueue;

int g_quit = 0;
PacketQueue audioq;
AVFrame wanted_frame;

void audio_callback(void *userdata, Uint8 *stream, int len);
void packet_queue_init(PacketQueue *q);
int packet_queue_put(PacketQueue *q, AVPacket *pkt);
int packet_queue_get(PacketQueue* q, AVPacket* pkt, int block);
int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size);

int main(int argc, char **argv)
{
    AVFormatContext *pFormateCtx = NULL;
    AVCodecContext *pCodecCtxOrig = NULL;
    AVCodecParameters *videoCodecParameter;
    AVCodecParameters *audioCodecParameter;
    AVCodecContext *pAudioCodecCtxOrig = NULL;
    AVCodec *pCodec = NULL;
    AVCodec *pAudioCodec = NULL;
    AVFrame *pFrame = NULL;
    AVFrame *pFrameRGB = NULL;
    uint8_t *buffer = NULL;
    int numBytes;
    int videoStream = -1;
    int audioStream = -1;
    struct SwsContext *sws_cts = NULL;
    int i;
    int ret;
    SDL_Window *screen;
    SDL_Surface *surface;
    SDL_Renderer* renderer;
    SDL_Texture* testexture;
    SDL_Event event;
    SDL_Rect rect;
    SDL_AudioSpec wanted_spec, spec;


    char filePath[256] = { 0 };

    if (argc < 2)
    {
        printf("Please input video name:");
        gets_s(filePath, 256);
    }
    else
    {
        strcpy_s(filePath, 256, argv[1]);
    }

    //初始化
    av_register_all();
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
    {
        printf_s("SDL_Init failed:%s\n", SDL_GetError());
        return -1;
    }

    //读取文件头和存储信息到pFormateCtx中
    if (avformat_open_input(&pFormateCtx, filePath, NULL, 0) != 0)
    {
        printf_s("avformat_open_input failed\n");
        return -1;
    }

    if (avformat_find_stream_info(pFormateCtx, NULL) < 0)
    {
        printf_s("avformat_find_stream_info failed\n");
        return -1;
    }
    av_dump_format(pFormateCtx, 0, filePath, 0);

    for (unsigned i = 0; i < pFormateCtx->nb_streams; ++i)
    {
        if (AVMEDIA_TYPE_VIDEO == pFormateCtx->streams[i]->codecpar->codec_type)
        {
            videoStream = i;
            continue;
        }

        if (AVMEDIA_TYPE_AUDIO == pFormateCtx->streams[i]->codecpar->codec_type)
        {
            audioStream = i;
            continue;
        }
    }

    if (-1 == videoStream)
    {
        printf_s("Can't find video stream\n");
        return -1;
    }


    if (-1 == audioStream)
    {
        printf_s("Can't find audio stream\n");
        return -1;
    }

    //找视频解码器
    videoCodecParameter = pFormateCtx->streams[videoStream]->codecpar;
    pCodec = avcodec_find_decoder(videoCodecParameter->codec_id);
    if (NULL == pCodec)
    {
        printf_s("avcodec_find_decoder failed\n");
        return -1;
    }

    pCodecCtxOrig = avcodec_alloc_context3(pCodec);
    if (NULL == pCodecCtxOrig)
    {
        printf_s("avcodec_alloc_context3 failed\n");
        return -1;
    }

    if (avcodec_parameters_to_context(pCodecCtxOrig, videoCodecParameter) < 0)
    {
        printf_s("avcodec_parameters_to_context failed\n");
        return -1;
    }

    if (avcodec_open2(pCodecCtxOrig, pCodec, NULL) < 0)
    {
        printf_s("avcodec_open2 failed\n");
        return -1;
    }

    //找音频解码器
    audioCodecParameter = pFormateCtx->streams[audioStream]->codecpar;
    pAudioCodec = avcodec_find_decoder(audioCodecParameter->codec_id);
    if (NULL == pAudioCodec)
    {
        printf_s("avcodec_find_decoder failed audio\n");
        return -1;
    }

    pAudioCodecCtxOrig = avcodec_alloc_context3(pAudioCodec);
    if (NULL == pCodecCtxOrig)
    {
        printf_s("avcodec_alloc_context3 failed audio\n");
        return -1;
    }

    if (avcodec_parameters_to_context(pAudioCodecCtxOrig, audioCodecParameter) < 0)
    {
        printf_s("avcodec_parameters_to_context failed\n");
        return -1;
    }

    ret = avcodec_open2(pAudioCodecCtxOrig, pAudioCodec, NULL);


    memset(&wanted_spec, 0, sizeof(wanted_spec));
    wanted_spec.freq = pAudioCodecCtxOrig->sample_rate;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = pAudioCodecCtxOrig->channels;
    wanted_spec.silence = 0;
    wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = pAudioCodecCtxOrig;

    if (SDL_OpenAudio(&wanted_spec, &spec) < 0)
    {
        printf_s("SDL_OpenAudio failed\n");
        return -1;
    }

    wanted_frame.format = AV_SAMPLE_FMT_S16;
    wanted_frame.sample_rate = spec.freq;
    wanted_frame.channel_layout = av_get_default_channel_layout(spec.channels);
    wanted_frame.channels = spec.channels;

    packet_queue_init(&audioq);
    SDL_PauseAudio(0);

    pFrame = av_frame_alloc();
    pFrameRGB = av_frame_alloc();

    numBytes = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtxOrig->width, pCodecCtxOrig->height, 1);
    buffer = (uint8_t *)av_mallocz(numBytes * sizeof(uint8_t));
    memset(buffer, 0, numBytes * sizeof(uint8_t));

    ret = av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_YUV420P, pCodecCtxOrig->width, pCodecCtxOrig->height, 1);

    AVPacket packet;
    sws_cts = sws_getContext(
        pCodecCtxOrig->width,
        pCodecCtxOrig->height,
        pCodecCtxOrig->pix_fmt,
        pCodecCtxOrig->width,
        pCodecCtxOrig->height,
        AV_PIX_FMT_YUV420P,
        SWS_BILINEAR,
        NULL, NULL, NULL);

    screen = SDL_CreateWindow("Hell's Player", 
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 
        pCodecCtxOrig->width, pCodecCtxOrig->height, SDL_WINDOW_SHOWN);
    surface = SDL_GetWindowSurface(screen);
    renderer = SDL_CreateRenderer(screen, -1, 0);
    testexture = SDL_CreateTexture(
        renderer,
        SDL_PIXELFORMAT_IYUV,
        SDL_TEXTUREACCESS_STREAMING,
        pCodecCtxOrig->width, pCodecCtxOrig->height);

    rect.x = 0;
    rect.y = 0;
    rect.h = pCodecCtxOrig->height;
    rect.w = pCodecCtxOrig->width;

    i = 0;
    while (av_read_frame(pFormateCtx, &packet) >= 0)
    {

        if (packet.stream_index == videoStream)
        {


            ret = avcodec_send_packet(pCodecCtxOrig, &packet);
            switch (ret)
            {
            case 0:
                break;

            case AVERROR(EAGAIN):
                return -1;

            case AVERROR_EOF:
                return -1;

            case AVERROR(EINVAL):
                return -1;

            case AVERROR(ENOMEM):
                return -1;
            }
            ret = avcodec_receive_frame(pCodecCtxOrig, pFrame);
            switch (ret)
            {
            case 0:
                break;

            case AVERROR(EAGAIN):
                printf_s("input is not accepted right now - the packet must be resent after trying to read output\n");
                continue;

            case AVERROR_EOF:
                return -1;

            case AVERROR(EINVAL):
                return -1;
            }

            ret = sws_scale(sws_cts, (uint8_t const* const *)pFrame->data,
                pFrame->linesize, 0, pCodecCtxOrig->height,
                pFrameRGB->data, pFrameRGB->linesize);


            SDL_UpdateTexture(testexture, &rect, pFrameRGB->data[0], pFrameRGB->linesize[0]);
            SDL_RenderClear(renderer);
            SDL_RenderCopy(renderer, testexture, &rect, &rect);
            SDL_RenderPresent(renderer);
        }
        else if (audioStream == packet.stream_index)
        {
            packet_queue_put(&audioq, &packet);
        }
        SDL_PollEvent(&event);
        if (SDL_QUIT == event.type)
        {
            g_quit = 1;
            break;
        }

    }

    av_packet_unref(&packet);
    av_free(buffer);
    av_free(pFrameRGB);
    av_free(pFrame);
    avformat_close_input(&pFormateCtx);
    return 0;
}


// 包队列初始化
void packet_queue_init(PacketQueue* q)
{
    //memset(q, 0, sizeof(PacketQueue));
    q->last_pkt = NULL;
    q->first_pkt = NULL;
    q->mutext = SDL_CreateMutex();
    q->cond = SDL_CreateCond();
}

// 放入packet到队列中,不带头指针的队列
int packet_queue_put(PacketQueue*q, AVPacket *pkt)
{
    AVPacketList *pktl;
    AVPacket *newPkt;
    newPkt = (AVPacket*)av_mallocz_array(1, sizeof(AVPacket));
    if (av_packet_ref(newPkt, pkt) < 0)
        return -1;

    pktl = (AVPacketList*)av_malloc(sizeof(AVPacketList));
    if (!pktl)
        return -1;

    pktl->pkt = *newPkt;
    pktl->next = NULL;

    SDL_LockMutex(q->mutext);

    if (!q->last_pkt) // 队列为空,新插入元素为第一个元素
        q->first_pkt = pktl;
    else // 插入队尾
        q->last_pkt->next = pktl;

    q->last_pkt = pktl;

    q->nb_packets++;
    q->size += newPkt->size;

    SDL_CondSignal(q->cond);
    SDL_UnlockMutex(q->mutext);

    return 0;
}

// 从队列中取出packet
static int packet_queue_get(PacketQueue* q, AVPacket* pkt, int block)
{
    AVPacketList* pktl;
    int ret;

    SDL_LockMutex(q->mutext);

    while (1)
    {
        if (g_quit)
        {
            ret = -1;
            break;
        }

        pktl = q->first_pkt;
        if (pktl)
        {
            q->first_pkt = pktl->next;
            if (!q->first_pkt)
                q->last_pkt = NULL;

            q->nb_packets--;
            q->size -= pktl->pkt.size;

            *pkt = pktl->pkt;
            av_free(pktl);
            ret = 1;
            break;
        }
        else if (!block)
        {
            ret = 0;
            break;
        }
        else
        {
            SDL_CondWait(q->cond, q->mutext);
        }
    }

    SDL_UnlockMutex(q->mutext);

    return ret;
}

// 解码音频数据
int audio_decode_frame(AVCodecContext* aCodecCtx, uint8_t* audio_buf, int buf_size)
{
    static AVPacket pkt;
    static uint8_t* audio_pkt_data = NULL;
    static int audio_pkt_size = 0;
    static AVFrame frame;

    int len1;
    int data_size = 0;

    SwrContext* swr_ctx = NULL;

    while (1)
    {
        while (audio_pkt_size > 0)
        {
            int got_frame = 0;
            //len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
            avcodec_send_packet(aCodecCtx, &pkt);
            avcodec_receive_frame(aCodecCtx, &frame);
            len1 = frame.pkt_size;
            if (len1 < 0) // 出错,跳过
            {
                audio_pkt_size = 0;
                break;
            }

            audio_pkt_data += len1;
            audio_pkt_size -= len1;
            data_size = 0;
            if (got_frame)
            {
                int linesize = 1;
                data_size = av_samples_get_buffer_size(&linesize, aCodecCtx->channels, frame.nb_samples, aCodecCtx->sample_fmt, 1);
                assert(data_size <= buf_size);
                memcpy(audio_buf, frame.data[0], data_size);
            }

            if (frame.channels > 0 && frame.channel_layout == 0)
                frame.channel_layout = av_get_default_channel_layout(frame.channels);
            else if (frame.channels == 0 && frame.channel_layout > 0)
                frame.channels = av_get_channel_layout_nb_channels(frame.channel_layout);

            if (swr_ctx)
            {
                swr_free(&swr_ctx);
                swr_ctx = NULL;
            }

            swr_ctx = swr_alloc_set_opts(NULL, wanted_frame.channel_layout, wanted_frame.format, wanted_frame.sample_rate,
                frame.channel_layout, frame.format, frame.sample_rate, 0, NULL);

            if (!swr_ctx || swr_init(swr_ctx) < 0)
            {
                printf_s("swr_init failed\n");
                break;
            }

            int dst_nb_samples = (int)av_rescale_rnd(swr_get_delay(swr_ctx, frame.sample_rate) + frame.nb_samples,
                wanted_frame.sample_rate, wanted_frame.format, 1);
            int len2 = swr_convert(swr_ctx, &audio_buf, dst_nb_samples,
                (const uint8_t**)frame.data, frame.nb_samples);
            if (len2 < 0)
            {
                printf_s("swr_convert failed\n");
                break;
            }

            return wanted_frame.channels * len2 * av_get_bytes_per_sample(wanted_frame.format);

            if (data_size <= 0)
                continue; // No data yet,get more frames

            return data_size; // we have data,return it and come back for more later
        }

        if (pkt.data)
            av_packet_unref(&pkt);

        if (g_quit)
            return -1;

        if (packet_queue_get(&audioq, &pkt, 1) < 0)
            return -1;

        audio_pkt_data = pkt.data;
        audio_pkt_size = pkt.size;
    }
}

// 解码后的回调函数
void audio_callback(void* userdata, Uint8* stream, int len)
{
    AVCodecContext* aCodecCtx = (AVCodecContext*)userdata;
    int len1, audio_size;

    static uint8_t audio_buff[192000 * 3 / 2];
    static unsigned int audio_buf_size = 0;
    static unsigned int audio_buf_index = 0;

    SDL_memset(stream, 0, len);

    while (len > 0)
    {
        if (audio_buf_index >= audio_buf_size)
        {
            audio_size = audio_decode_frame(aCodecCtx, audio_buff, sizeof(audio_buff));
            if (audio_size < 0)
            {
                audio_buf_size = 1024;
                memset(audio_buff, 0, audio_buf_size);
            }
            else
                audio_buf_size = audio_size;

            audio_buf_index = 0;
        }
        len1 = audio_buf_size - audio_buf_index;
        if (len1 > len)
            len1 = len;

        SDL_MixAudio(stream, audio_buff + audio_buf_index, len, SDL_MIX_MAXVOLUME);


        //memcpy(stream, (uint8_t*)(audio_buff + audio_buf_index), audio_buf_size);
        len -= len1;
        stream += len1;
        audio_buf_index += len1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值