天天看點

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() << "解碼器名稱:" <<pAVCodec->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()<<SDL_GetAudioDeviceName(count,0);
    }
    if((deviceID = SDL_OpenAudioDevice(NULL,0,&spec,NULL,SDL_AUDIO_ALLOW_ANY_CHANGE))<2)
    {
        qDebug()<<"SDL_OpenAudioDevice with error deviceID : " << deviceID<<SDL_GetError();
        return false;
    }
    SDL_PauseAudioDevice(deviceID,0);
    return true;
}
           

  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:"<<pAVFrame->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的部落格

繼續閱讀