04使用ffmpeg解码视频之SDL的使用

开发环境:Windows 10, Qt 5.13.1, ffmpeg 4.2.2

前面介绍了使用FFMPEG+Qt解码视频并显示。

本篇介绍声音播放,这里使用SDL作为音频播放功能,

首先下载SDL

SDL官网地址:http://www.libsdl.org/

我们现在都是在Windows系统下使用,因此直接下载编译好的版本就行了。

基本上的库Windows的版本都有提供已经编译好的版本,SDL也是如此:

 

创建QT的工程,方法和前面一样

QfFFmpegSDLAudio.pro内容如下:

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

#将输出文件直接放到源码目录上一级的win的bin目录下,将dll都放在了次目录中,用以解决运行后找不到dll的问#DESTDIR=$$PWD/bin/
contains(QT_ARCH, i386) {
    message("32-bit")
    DESTDIR = $${PWD}/../win/bin32

    message($$DESTDIR)
} else {
    message("64-bit")
    DESTDIR = $${PWD}/../win/bin64
}

win32{
contains(QT_ARCH, i386){
    message("32-bit")
    QMAKE_LFLAGS += -shared
    INCLUDEPATH += $$PWD/../win/bin32/include
    INCLUDEPATH += $$PWD/../win/bin32/include/SDL2

    LIBS += -L$$PWD/../win/bin32/lib -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale
    LIBS += -L$$PWD/../win/bin32/lib -lSDL2
} else {
    message("64-bit")
    QMAKE_LFLAGS += -shared
    INCLUDEPATH += $$PWD/../win/bin64/include
    INCLUDEPATH += $$PWD/../win/bin64/include/SDL2

    LIBS += -L$$PWD/../win/bin64/lib -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale
    LIBS += -L$$PWD/../win/bin64/lib/libSDL2
}

}

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    int playAudio(char *filePath);

public slots:
    void slotOpenAudio();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>

extern "C"
{
#include "libavcodec/avcodec.h" //封装格式
#include "libavformat/avformat.h" //解码
#include "libswscale/swscale.h" //缩放
#include "libavdevice/avdevice.h"

#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"

#include <SDL.h>
//#include <SDL_audio.h>
//#include <SDL_types.h>
//#include <SDL_name.h>
//#include <SDL_main.h>
//#include <SDL_config.h>
}

//==================================
#define SDL_AUDIO_BUFFER_SIZE 1024
#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio

///由于我们建立的是C++的工程
///编译的时候使用的C++的编译器编译
///而FFMPEG是C的库
///因此这里需要加上extern "C"
///否则会提示各种未定义
typedef struct PacketQueue {
    AVPacketList *first_pkt, *last_pkt;
    int nb_packets;
    int size;
    SDL_mutex *mutex;
    SDL_cond *cond;
} PacketQueue;

// 分配解码过程中的使用缓存
//AVFrame* audioFrame = avcodec_alloc_frame();
AVFrame* audioFrame = av_frame_alloc();
PacketQueue *audioq;

void packet_queue_init(PacketQueue *q) {
    memset(q, 0, sizeof(PacketQueue));
    q->mutex = SDL_CreateMutex();
    q->cond = SDL_CreateCond();
}

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {

    AVPacketList *pkt1;
    if (av_dup_packet(pkt) < 0) {
        return -1;
    }
    pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList));
    if (!pkt1)
        return -1;
    pkt1->pkt = *pkt;
    pkt1->next = NULL;

    SDL_LockMutex(q->mutex);

    if (!q->last_pkt)
        q->first_pkt = pkt1;
    else
        q->last_pkt->next = pkt1;
    q->last_pkt = pkt1;
    q->nb_packets++;
    q->size += pkt1->pkt.size;
    SDL_CondSignal(q->cond);

    SDL_UnlockMutex(q->mutex);
    return 0;
}

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
    AVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex);

    for (;;) {

        pkt1 = q->first_pkt;
        if (pkt1) {
            q->first_pkt = pkt1->next;
            if (!q->first_pkt)
                q->last_pkt = NULL;
            q->nb_packets--;
            q->size -= pkt1->pkt.size;
            *pkt = pkt1->pkt;
            av_free(pkt1);
            ret = 1;
            break;
        } else if (!block) {
            ret = 0;
            break;
        } else {
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);
    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;
    int len1, data_size;

    for(;;)
    {
        if(packet_queue_get(audioq, &pkt, 1) < 0)
        {
            return -1;
        }
        audio_pkt_data = pkt.data;
        audio_pkt_size = pkt.size;
        while(audio_pkt_size > 0)
        {
            int got_picture;

            int ret = avcodec_decode_audio4( aCodecCtx, audioFrame, &got_picture, &pkt);
            printf("ret======%d   got_picture======%d\n", ret, got_picture);
            if( ret < 0 ) {
                printf("Error in decoding audio frame.\n");
                exit(0);
            }

            if( got_picture ) {
                int in_samples = audioFrame->nb_samples;
                short *sample_buffer = (short*)malloc(audioFrame->nb_samples * 2 * 2);
                memset(sample_buffer, 0, audioFrame->nb_samples * 4);

                int i=0;
                float *inputChannel0 = (float*)(audioFrame->extended_data[0]);

                // Mono
                if( audioFrame->channels == 1 ) {
                    for( i=0; i<in_samples; i++ ) {
                        float sample = *inputChannel0++;
                        if( sample < -1.0f ) {
                            sample = -1.0f;
                        } else if( sample > 1.0f ) {
                            sample = 1.0f;
                        }

                        sample_buffer[i] = (int16_t)(sample * 32767.0f);
                    }
                } else { // Stereo
                    float* inputChannel1 = (float*)(audioFrame->extended_data[1]);
                    for( i=0; i<in_samples; i++) {
                        sample_buffer[i*2] = (int16_t)((*inputChannel0++) * 32767.0f);
                        sample_buffer[i*2+1] = (int16_t)((*inputChannel1++) * 32767.0f);
                    }
                }
//                fwrite(sample_buffer, 2, in_samples*2, pcmOutFp);
                memcpy(audio_buf, sample_buffer, in_samples * 4);
                free(sample_buffer);
            }

            audio_pkt_size -= ret;

            if (audioFrame->nb_samples <= 0)
            {
                continue;
            }

            data_size = audioFrame->nb_samples * 4;

            return data_size;
        }
        if(pkt.data)
            av_free_packet(&pkt);
   }
}

void audio_callback(void *userdata, Uint8 *stream, int len)
{
    AVCodecContext *aCodecCtx = (AVCodecContext *) userdata;
    int len1, audio_data_size;

    static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
    static unsigned int audio_buf_size = 0;
    static unsigned int audio_buf_index = 0;

    /*   len是由SDL传入的SDL缓冲区的大小,如果这个缓冲未满,我们就一直往里填充数据 */
    while (len > 0) {
        /*  audio_buf_index 和 audio_buf_size 标示我们自己用来放置解码出来的数据的缓冲区,*/
        /*   这些数据待copy到SDL缓冲区, 当audio_buf_index >= audio_buf_size的时候意味着我*/
        /*   们的缓冲为空,没有数据可供copy,这时候需要调用audio_decode_frame来解码出更
         /*   多的桢数据 */

        if (audio_buf_index >= audio_buf_size) {
            audio_data_size = audio_decode_frame(aCodecCtx, audio_buf,sizeof(audio_buf));
            printf("audio_data_size===============%d\n", audio_data_size);
            /* audio_data_size < 0 标示没能解码出数据,我们默认播放静音 */
            if (audio_data_size < 0) {
                /* silence */
                audio_buf_size = 1024;
                /* 清零,静音 */
                memset(audio_buf, 0, audio_buf_size);
            } else {
                audio_buf_size = audio_data_size;
            }
            audio_buf_index = 0;
        }
        /*  查看stream可用空间,决定一次copy多少数据,剩下的下次继续copy */
        len1 = audio_buf_size - audio_buf_index;
        if (len1 > len) {
            len1 = len;
        }

        memcpy(stream, (uint8_t *) audio_buf + audio_buf_index, len1);
        len -= len1;
        stream += len1;
        audio_buf_index += len1;
    }
}


//===================================

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(ui->pushButton_openAudio, SIGNAL(clicked()), this, SLOT(slotOpenAudio()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::slotOpenAudio()
{
    char *path = (char*)"./test.aac";
    int ret = playAudio(path);
    qDebug() << "ret====================" << ret;
}

int MainWindow::playAudio(char *filepath)
{
    //这里简单的输出一个版本号
#ifdef _WIN64
    qDebug() << "Hello FFmpeg(64 bit位)!====================";
    cout << "Hello FFmpeg(64bit位)!" << endl;
#elif _WIN32
    qDebug() << "Hello FFmpeg(32 bit位)!====================";
#endif
    av_register_all();//ffmpeg 4.2.2已经不需要这个函数了
    unsigned version = avcodec_version();
    qDebug() << "version is====================" << version;
    //目前这里只能用aac的文件 试过mp3播放不正常 其他没有测试
    //可能是ffmpeg自带的解码器解码mp3有问题  以后再研究了
    //这个例子的重点是讲SDL的使用 因此不管他了
    //(记住路径不要有中文)
    //char *filename = (char *)"./test.aac";//播放OK
    //char *filename = (char *)"../mp4/test.aac";//播放OK
    //char *filename = (char *)"../mp4/in.aac";//播放OK
    char *filename = (char *)"../mp4/mozart.mp3";//播放OK

    AVFormatContext* pFormatCtx = avformat_alloc_context();
    if( avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0 ) {
        printf("Couldn't open file.\n");
        return -1;
    }
    else
    {
        printf("open file success====%s.\n", filename);
    }

    // Retrieve stream information
    if( avformat_find_stream_info(pFormatCtx, NULL) < 0 ) {
        printf("Couldn't find stream information.\n");
        return -1;
    }
    else
    {
        printf("find stream information========.\n");
    }

    // Dump valid information onto standard error
    av_dump_format(pFormatCtx, 0, filename, false);

    ///循环查找包含的音频流信息,直到找到音频类型的流
    ///便将其记录下来 保存到audioStream变量中
    int audioStream = -1;
    for(unsigned int i=0; i < pFormatCtx->nb_streams; i++) {
        if( pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) {
            audioStream = i;
            break;
        }
    }

    printf("pFormatCtx->nb_streams========%d\n", pFormatCtx->nb_streams);
    ///如果audioStream为-1 说明没有找到音频流
    if( audioStream == -1 ) {
        printf("Didn't find a audio stream.\n");
        return -1;
    }
    else
    {
        printf("find a audio stream======audioStream==%d.\n", audioStream);
    }

    // Get a pointer to the codec context for the audio stream
    AVCodecContext* audioCodecCtx = pFormatCtx->streams[audioStream]->codec;

    ///查找解码器
    AVCodec* pCodec = avcodec_find_decoder(audioCodecCtx->codec_id);
    if( pCodec == NULL ) {
        printf("Codec not found.\n");
        return -1;
    }
    else
    {
        printf("find Codec========.\n");
    }

    ///打开解码器
    AVDictionary* options = NULL;
    if( avcodec_open2(audioCodecCtx, pCodec, &options) < 0 ) {
        printf("Could not open codec.\n");
        return -1;
    }
    else
    {
        printf("open Codec========.\n");
    }

    ///  打开SDL播放设备 - 开始
    SDL_LockAudio();
    SDL_AudioSpec spec;
    SDL_AudioSpec wanted_spec;
    printf("audioCodecCtx->sample_rate========%d\n", audioCodecCtx->sample_rate);

    wanted_spec.freq = audioCodecCtx->sample_rate;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = audioCodecCtx->channels;
    wanted_spec.silence = 0;
    wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = audioCodecCtx;
    if(SDL_OpenAudio(&wanted_spec, &spec) < 0)
    {
        fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
        return 0;
    }
    else
    {
        printf("SDL_OpenAudio success========.\n");
    }
    SDL_UnlockAudio();
    SDL_PauseAudio(0);
    ///  打开SDL播放设备 - 结束
    printf("打开SDL播放设备 - 结束=======================================.\n");
    //初始化音频队列
    audioq = new PacketQueue;
    packet_queue_init(audioq);

    AVPacket *packet = (AVPacket *)malloc(sizeof(AVPacket));
    av_init_packet(packet);

    // 分配解码过程中的使用缓存
    AVFrame* audioFrame = av_frame_alloc();
    // Debug -- Begin
    printf("比特率 %3d\n", pFormatCtx->bit_rate);
    printf("解码器名称 %s\n", audioCodecCtx->codec->long_name);
    printf("time_base  %d \n", audioCodecCtx->time_base);
    printf("声道数  %d \n", audioCodecCtx->channels);
    printf("sample per second  %d \n", audioCodecCtx->sample_rate);
    // Debug -- End
    int nCount = 0;
    while(1)
    {
        int nFrame = av_read_frame(pFormatCtx, packet);
        printf("nFrame=========================%d  nCount======%d\n", nFrame, ++nCount);
        if(nFrame < 0)
        //if (av_read_frame(pFormatCtx, packet) < 0 )
        {
            break; //这里认为音频读取完了
        }
        printf("packet->stream_index===%d,  audioStream====%d\n", packet->stream_index, audioStream);
        if( packet->stream_index == audioStream )
        {
            int npacket = packet_queue_put(audioq, packet);
            printf("npacket=======%d\n", npacket);
            //这里我们将数据存入队列 因此不调用 av_free_packet 释放
        }
        else
        {
            // Free the packet that was allocated by av_read_frame
            av_free_packet(packet);
        }

        SDL_Delay(10);
    }

    printf("read finished!\n");

    av_free(audioFrame);
    avcodec_close(audioCodecCtx);// Close the codec
    avformat_close_input(&pFormatCtx);// Close the video file

    return 0;
}

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

参考:

https://blog.csdn.net/qq214517703/article/details/52618988
https://blog.csdn.net/yinsui1839/article/details/80519742
https://blog.csdn.net/qq_37933895/article/details/100015875

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值