天天看點

Rtsp拉流錄制MP4

最近做了個項目,是rtsp拉流儲存成MP4,由于之前已經完成本地錄制,是以隻要稍微改一下就可以了,順便寫個部落格。

平台:ARM+linux

用到的庫:ffmpeg

一、首先是拉流

拉流直接使用av_read_frame直接就可以。然後分辯其stream_index即可,video_stream_idx和audio_stream_idx是初始化input确定的。

我這裡直接儲存入隊列,防止IO操作時間太長,阻塞拉流。

while()
{
    AVPacket pkt;
    av_read_frame(Gparg->ifmt_ctx, &pkt);  
    if(pkt.stream_index==Gparg->video_stream_idx)
    {
        video_Enqueue(&pkt,Gparg);
    }
    else if(pkt.stream_index==Gparg->audio_stream_idx)
    {
       audio_Enqueue(&pkt,Gparg);
    }
    av_packet_unref(&pkt);
}
           

二、入隊出隊

儲存隻需要pts、dts、size、stream_index、data這些資料,根據我的需求,我沒有使用ffmpeg裡複制AVPacket的函數,是以我就直接複制和拷貝了。

這裡我使用的是循環隊列,循環隊列的好處是有一大塊連續的空間,釋放的時候比連結清單友善,操作也友善。

入隊關鍵代碼:

queue->avPacket[queue->rear]->size=pkt->size;
queue->avPacket[queue->rear]->stream_index=pkt->stream_index;
memcpy(queue->avPacket[queue->rear]->data,pkt->data,pkt->size);
           

由于我用循環隊列,是以直接指向就行了。這裡注意,我傳進來的是AVPacket** pkt,這樣指向位址才行的通。

出隊關鍵代碼:

三、錄制

我是用H264+aac完成的MP4錄制,直接複用即可。是以出隊的AVPacket直接寫入就行了。

本來是視訊和音頻一起寫的,也就是計算視訊和音頻的pts內插補點,那個小就寫入哪個。但是發現當視訊資料大量湧入,而視訊pts也很大,就會造成,寫很多音頻才寫一次視訊,是以為了避免這個,就分成2個線程寫了,記得用線程鎖。

錄制關鍵代碼:

for(;;)
{       
    if(Gparg->g_AVStopGetStream)//當退出時。這裡作flush用,把隊列裡的剩餘都寫出來
    {   
        while(!av_queue_isEmpty(v_queue))
        {
            ret=video_Dequeue(v_queue,&v_pkt,Gparg); 
            WriteVideo(v_pkt,Gparg);
        }               
        break;
    }
    else
    {
        {   
            v_lastpts=v_pts;
            ret=video_Dequeue(v_queue,&v_pkt,Gparg); 
            if(ret==&&(v_pkt != NULL)&& (v_pkt->size> ))
            { 
                v_lastpts=v_pkt->pts; 
                WriteVideo(v_pkt,Gparg); 
            }
            v_pts=v_lastpts; 
        }
    }
}
           

由于write_frame會清空v_pkt的指向,是以重新用了一個pkt。

WriteVideo函數關鍵代碼:

AVPacket pkt ;
    av_init_packet(&pkt);
    pkt.pts=v_pkt->pts;
    pkt.dts=v_pkt->dts;
    pkt.data=v_pkt->data;
    pkt.size=v_pkt->size;
    pkt.stream_index=v_pkt->stream_index;    
    pkt.flags|=AV_PKT_FLAG_KEY;

    pthread_mutex_lock (&Gparg->mutex);
    int ret=  av_interleaved_write_frame(Gparg->ofmt_ctx, &pkt);
    pthread_mutex_unlock (&Gparg->mutex);

    av_packet_unref(&pkt);

    if (ret < ) {
            SAMPLE_PRT( "  end   Error while writing video frame: %s\n", av_err2str(ret));
        return ret;
    }   
    return HI_SUCCESS;
           

四、額外幾個Tips

1、擷取inputfile的音視訊格式資訊

在ifmt_ctx->streams[i]->codecpar 裡,而幀數是在ifmt_ctx->streams[i]裡的avg_frame_rate

2、有時視訊播放時間不對,需要設定

AVDictionary *opt=NULL;
av_dict_set_int(&opt,"video_track_timescale",,);
ret=avformat_write_header(Gparg->ofmt_ctx,&opt);
           

後面如果有修改的還會再補充