ffmpeg--拉流RTSP/RTMP、多线程使用SDL播放音频

简介:

        看大家对之前的解码视频比较感兴趣,放一篇解码音频的文章。未经允许不可转载。

       因为我的需求是局域网内一台服务器,可能有多个客户端,服务器端显示音视频、客户端只播放音频。所以使用RTMP+流媒体服务器另类实现。这是客户端的部分实现,具体运行环境是ARM+QT5+FFMPEG+SDL2。

        优点是好实现,如果只做音频通话延时是没问题的。

        缺点是做视频时延时主要取决于流媒体服务器的质量以及关键帧的提取。

实现:

1、提前准备好QT5+FFMPEG+mp3lame+SDL2环境。

        搭环境就不说了。

2、在之前解码视频的基础上,封装解码音频类。

        懒得改了,类名还叫decode。就是从视频那变的。

  构造:

mdecode::mdecode(QString url,AVPixelFormat pix_fmt,AVDictionary* options): filename (url),mpix_fmt(pix_fmt)
{
    
}

析构:

mdecode::~mdecode()
{
    if(outbuffer) av_free(outbuffer);
    if(pSwsContext) sws_freeContext(pSwsContext);
    if(pAVFrameRgb) av_frame_free(&pAVFrameRgb);
    if(pAVFrame) av_frame_free(&pAVFrame);
    if(pAVpacket) av_packet_free(&pAVpacket);
    if(pAVCodecContext) avcodec_close(pAVCodecContext);
    if(pAVFormatContext) avformat_free_context(pAVFormatContext);
}

初始化ffmpeg:

bool mdecode::init_mdecode()
{
    int ret=0;
    pAVFormatContext = avformat_alloc_context();//分配全局上下文空间
    pAVpacket = av_packet_alloc();              //分配数据包空间
    pAVFrame  = av_frame_alloc();               //分配单帧空间
    pAVFrameRgb  = av_frame_alloc();           //分配rgb单帧空间
    if(!pAVFormatContext || !pAVpacket || !pAVFrame || !pAVFrameRgb)
    {
        qDebug()<< "init_mdecode failed";
        return false;
    }
    AVDictionary *optionsDict = NULL;
    //av_dict_set(&optionsDict, "buffer_size", "1024000", 0); //设置缓存大小,1080p可将值调大

    av_dict_set(&optionsDict, "rtsp_transport", "udp", 0);
    //av_dict_set(&optionsDict, "stimeout", "500000", 0); //设置超时断开连接时间,单位微秒 3s 试了没用
    //av_dict_set(&optionsDict, "listen_timeout", "1", 0);//试了没用
    //av_dict_set(&optionsDict, "max_delay", "30000000", 0);//试了没用

    pAVFormatContext->interrupt_callback.callback = decode_interrupt_cb;
    pAVFormatContext->interrupt_callback.opaque = (void*)this;
    time_ms_out=2000;//阻塞1000ms
    m_nStartOpenTS = av_gettime();

    ret = avformat_open_input(&pAVFormatContext, filename.toUtf8().data(), 0, &optionsDict);
    m_nStartOpenTS = 0;

    if(ret)
    {
        qDebug() << "Failed to avformat_open_input(&pAVFormatContext, filename.toUtf8().data(), 0, 0)";
        return false;
    }
    /*
     * 探测流媒体信息。
    */
    ret = avformat_find_stream_info(pAVFormatContext, 0);
    if(ret < 0)
    {
        qDebug() << "Failed to avformat_find_stream_info(pAVCodecContext, 0)";
        return false;
    }
    av_dump_format(pAVFormatContext, 0, filename.toUtf8().data(), 0);//打印文件中包含的格式信息

    for(int index = 0; index < pAVFormatContext->nb_streams; index++) //遍历寻找视频流
    {
        pAVCodecContext = pAVFormatContext->streams[index]->codec;
        if(pAVCodecContext->codec_type==AVMEDIA_TYPE_AUDIO)
        {
            audioIndex = index;//此处只找音频
            break;
        }
    }
    if(audioIndex == -1 || !pAVCodecContext)
    {
        qDebug() << "Failed to find audio stream";
        return false;
    }
    /*
        查找解码器并打开。
    */
    pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
    if(!pAVCodec)
    {
        qDebug() << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"<< pAVCodecContext->codec_id;
        return false;
    }
    QString url_type=filename.mid(0,4);

    if(url_type=="rtsp"||url_type=="rtmp")
    {
        if(avcodec_open2(pAVCodecContext, pAVCodec, &optionsDict))
        {
            qDebug() <<"Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
            return false;
        }
    }
    else
    {
        if(avcodec_open2(pAVCodecContext, pAVCodec, NULL))
        {
            qDebug() <<"Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
            return false;
        }
    }
    qDebug() << "解码器名称:" <name
            << "通道数:" << pAVCodecContext->channels
            << "采样率:" << pAVCodecContext->sample_rate
            << "采样格式:" << pAVCodecContext->sample_fmt;

    pSwrContext = swr_alloc_set_opts(0,
                       av_get_default_channel_layout(2),outFormat,pAVCodecContext->sample_rate,
                       pAVCodecContext->channel_layout,pAVCodecContext->sample_fmt,pAVCodecContext->sample_rate,0,0);
    if(swr_init(pSwrContext)<0)
    {
        qDebug()<<"failed to swr init ";
        return false;
    }
    if(!init_sdl())
    {
        qDebug()<<"failed to sdl2 init ";
        return false;
    }
    run_flag=true;
    return true;
}

 tips:一大堆ffmpeg相关的API 。查找官方手册或者参考之前解码视频的代码注释。

初始化sdl:

bool mdecode::init_sdl()
{
    SDL_Init(SDL_INIT_AUDIO);

    spec.freq = 44100;
    spec.format = AUDIO_S16SYS;
    spec.channels = 2;
    spec.silence = 0;
    spec.samples = 1024;
    spec.callback =NULL;

    const int count = SDL_GetNumAudioDevices(0);
    for (int i = 0; i < count; ++i) {
        qDebug()<

  tips:SDL播放音频的方式不使用网上大多数的回调函数的方法,这种方法比较好用。

线程启动:

void mdecode::run()
{
    while(run_flag)
    {
        while(av_read_frame(pAVFormatContext,pAVpacket)>=0) //av_read_frame()读取一帧数据
        {   if(!run_flag) break;
            if(pAVpacket->stream_index==audioIndex)
            {
                if(avcodec_send_packet(pAVCodecContext, pAVpacket)) 
                {
                    qDebug() << "Failed to avcodec_send_packet(pAVCodecContext, pAVPacket)";
                    break;
                }
                while(!avcodec_receive_frame(pAVCodecContext, pAVFrame))
                {
                    //qDebug()<<"pavframe nb_samples:"<nb_samples;
                    swr_convert(pSwrContext,&pcm_buffer,2*44100,(const uint8_t **)pAVFrame->data,pAVFrame->nb_samples);
                    int out_buffer_size=av_samples_get_buffer_size(NULL, 2, pAVFrame->nb_samples, outFormat, 1);
                    SDL_QueueAudio(deviceID,pcm_buffer,out_buffer_size);
                }
            }
            
            av_free_packet(pAVpacket);
        }
    }
}

  tips:整体过程就是:解包-解码-转换-播放。ffmpeg的API参考官方的或者之前视频解码的代码注释。qt多线程怎么调用自行百度。

线程停止:

void mdecode::stop()
{
    SDL_CloseAudioDevice(deviceID);
    run_flag=false;

}

    tips:不关闭device的话下次打开会出问题。操作系统播放其他声音也会出问题。

上一篇:ffmpeg--拉流RTSP,解码后使用QT显示_eeeasen的博客

你可能感兴趣的:(图像处理,linux应用编程,音视频,qt,ffmpeg)