拉流

網(wǎng)絡(luò)攝像頭拉流

1.接收視頻數(shù)據(jù)

1.1結(jié)構(gòu)體

1.1.1編碼基本單位NALU

// NALU
typedef struct _MP4ENC_NaluUnit
{
    int type;
    int size;
    unsigned char *data;
    
}MP4ENC_NaluUnit;
/**
 * rational number numerator/denominator
 */
typedef struct AVRational{
    int num; ///< numerator
    int den; ///< denominator
} AVRational;

AVRational這個(gè)結(jié)構(gòu)標(biāo)識(shí)一個(gè)分?jǐn)?shù),num為分?jǐn)?shù)挨队,den為分母谷暮。

實(shí)際上time_base的意思就是時(shí)間的刻度:
如(1,25),那么時(shí)間刻度就是1/25; (1,90000)盛垦,那么時(shí)間刻度就是1/90000
那么湿弦,在刻度為1/25的體系下的time=5,轉(zhuǎn)換成在刻度為1/90000體系下的時(shí)間time為(51/25)/(1/90000) = 36005=18000

ffmpeg中做pts計(jì)算時(shí)情臭,存在大量這種轉(zhuǎn)換

在以下結(jié)構(gòu)中都有
AVCodecContext:編解碼上下文省撑。
AVStream:文件或其它容器中的某一個(gè)track。
如果由某個(gè)解碼器產(chǎn)生固定幀率的碼流
AVCodecContext中的AVRational根據(jù)幀率來設(shè)定俯在,如25幀竟秫,那么num = 1,den=25
AVStream中的time_base一般根據(jù)其采樣頻率設(shè)定跷乐,如(1肥败,90000)
在某些場景下涉及到PTS的計(jì)算時(shí),就涉及到兩個(gè)Time的轉(zhuǎn)換愕提,以及到底取哪里的time_base進(jìn)行轉(zhuǎn)換:

場景1:編碼器產(chǎn)生的幀馒稍,直接存入某個(gè)容器的AVStream中,那么此時(shí)packet的Time要從AVCodecContext的time轉(zhuǎn)換成目標(biāo)AVStream的time
場景2:從一種容器中demux出來的源AVStream的frame浅侨,存入另一個(gè)容器中某個(gè)目的AVStream纽谒。此時(shí)的時(shí)間刻度應(yīng)該從源AVStream的time,轉(zhuǎn)換成目的AVStream timebase下的時(shí)間如输。
其實(shí)鼓黔,問題的關(guān)鍵還是要理解央勒,不同的場景下取到的數(shù)據(jù)幀的time是相對哪個(gè)時(shí)間體系的。
demux出來的幀的time:是相對于源AVStream的timebase
編碼器出來的幀的time:是相對于源AVCodecContext的timebase
mux存入文件等容器的time:是相對于目的AVStream的timebase

這里的time指pts澳化。
http://blog.csdn.net/peckjerry/article/details/48344389

1.1.3錄像文件信息RecordInfo

typedef struct tag_RECORD_INFO
{
    int m_nCurPts;          //當(dāng)前位置的pts pts presentation time stamp 度量解碼后的視頻幀什么時(shí)候被顯示出來
    double m_dCurTime;      //當(dāng)前位置的時(shí)間(沒有使用)
    uint m_nTotalTime;      //總時(shí)長
    uint m_nLastTimestamp;  //上一個(gè)位置的時(shí)間戳
}RecordInfo;

1.1.4 時(shí)間計(jì)時(shí)器
//時(shí)間計(jì)時(shí)器

unsigned int _getTickCount() {
    
    struct timeval tv;
    //gettimeofday 獲取當(dāng)前精確時(shí)間
    /*
     int gettimeofday(struct timeval*tv, struct timezone *tz);
     其參數(shù)tv是保存獲取時(shí)間結(jié)果的結(jié)構(gòu)體崔步,參數(shù)tz用于保存時(shí)區(qū)結(jié)果:
     struct timezone{
     int tz_minuteswest;//格林威治時(shí)間往西方的時(shí)差
    int tz_dsttime;DST //時(shí)間的修正方式
}
timezone 參數(shù)若不使用則傳入NULL即可。
     */
    if (gettimeofday(&tv, NULL) != 0)
        return 0;
    
    return (tv.tv_sec * 1000 + tv.tv_usec / 1000);
}

1.2屬性

1.2.1 MP4VideoRecorder的屬性

@interface Mp4VideoRecorder : NSObject
{
    bool m_bRecord;             //錄像狀態(tài)
    
    NSString* m_strRecordFile;  //錄像文件存放路徑
    
    int m_nFileTotalSize;       //文件總大小
    
    RecordInfo m_stFrameInfo;   //視頻信息
    RecordInfo m_stAudioInfo;   //音頻信息
    
    
    AVStream            *m_pVideoSt; //視頻
    AVStream            *m_pAudioSt; //音頻
    AVFormatContext     *m_pFormatCtx;
    
    int                 m_nWidth;
    int                 m_nHeight;
    int                 m_nFrameRate;
    double              startTimeStamp;
    
    int                 firstIFrame_states;
    int                 startRecord_states;

    
    MP4_RecordAvccBox   m_avcCBox;
    
    AACEncodeConfig*            g_aacEncodeConfig;
    
    int                         g_writeRemainSize;
    int                         g_bufferRemainSize;
    
    NSLock*                     videoLock;
    
    
}

1.2核心代碼

- (void)doRecvVideo
{

    
    int readSize = -1;
    char *recvBuf = malloc(RECV_VIDEO_BUFFER_SIZE);
    
    FRAMEINFO_t frmInfo = {0};

    unsigned int frmNo = 0,prevFrmNo=0x0FFFFFFF;


    int outBufSize = 0, outFrmSize = 0, outFrmInfoSize = 0;

    H264Decoder*     decoder=[[H264Decoder alloc] init];
    decoder.updateDelegate=self;
    
    unsigned int timestamp=_getTickCount();
    
    
    int videoBps=0;
    
    if (sessionID >= 0 && avIndex >= 0)
    {

        avClientCleanVideoBuf(avIndex);

        
        while (isRunningRecvVideoThread)
        {
           
           
            unsigned int now = _getTickCount();
            
            if (now - timestamp > 1000)
            {

                dispatch_async(dispatch_get_main_queue(), ^{
                    
                    if (self.cameraDelegate &&[self.cameraDelegate respondsToSelector:@selector(camera:frameInfoWithVideoBPS:)])
                    {
                        [self.cameraDelegate camera:self frameInfoWithVideoBPS:videoBps];
                    }
                        
                });
                
                timestamp = now;
                videoBps = 0;
            }
            
            //usleep(1*1000);
            
  
            readSize = avRecvFrameData2(avIndex, recvBuf, RECV_VIDEO_BUFFER_SIZE, &outBufSize, &outFrmSize, (char *)&frmInfo, sizeof(frmInfo), &outFrmInfoSize, &frmNo);

            
            if (readSize == AV_ER_BUFPARA_MAXSIZE_INSUFF)
            {

                continue;
                
            }else if (readSize ==  AV_ER_MEM_INSUFF)
            {
                continue;
            } else if(readSize == AV_ER_INCOMPLETE_FRAME)
            {
                continue;
            }
            else if (readSize == AV_ER_LOSED_THIS_FRAME)
            {
                continue;
            }
            else if (readSize == AV_ER_DATA_NOREADY)
            {
                usleep(10*1000);
                continue;
                
            }
            else if (readSize >= 0)
            {
                if (frmInfo.flags == IPC_FRAME_FLAG_IFRAME || frmNo == (prevFrmNo + 1))
                {
                    
                    prevFrmNo = frmNo;
                    if (frmInfo.codec_id == MEDIA_CODEC_VIDEO_H264)
                    {
                        //printf("RECV H.264 ======================size: %d\n",readSize);
                        [[Mp4VideoRecorder getInstance] writeFrameData:(unsigned char *)recvBuf withSize:readSize];
                        
                        [decoder DecodeH264Frames:(unsigned char*)recvBuf withLength:readSize];
                      
                    }
                    videoBps+=readSize;
                }
                else
                {
                    NSLog(@"\t[H264] Incorrect %@ frame no(%d), prev:%d -> drop frame", (frmInfo.flags == IPC_FRAME_FLAG_IFRAME ? @"I" : @"P"), frmNo, prevFrmNo);
                    usleep(1*1000);
                    continue;
                }

                
            }
            else
            {
                usleep(1*1000);
                continue;
                
            }


        }

    }
    else
    {
        free(recvBuf);
        
        decoder.updateDelegate=nil;
        [decoder release];
        
        printf("doRecvVideo: [sessionID < 0 || avIndex < 0]\n");
        return;
    }



    free(recvBuf);
    
    decoder.updateDelegate=nil;
    [decoder release];
    
    NSLog(@"\t=== RecvVideo Thread Exit ===\n");
    

}

2.寫入視頻

- (void)writeFrameData: (unsigned char *)pszData withSize: (int)aSize
{
    if (!m_bRecord)
    {
        return;
    }
    if(aSize<=4){
        return;
    }
    
    
    MP4ENC_NaluUnit nalu;
    memset(&nalu, 0, sizeof(nalu));
    
    int  len = 0;
    int pos = 0;
    
    
    memset(&m_avcCBox, 0, sizeof(m_avcCBox));
    memset(m_avcCBox.ppsBuffer, 0, sizeof(m_avcCBox.ppsBuffer));
    memset(m_avcCBox.spsBuffer, 0, sizeof(m_avcCBox.spsBuffer));
    
    
    while ((len = [self readOneNaluFromBuffer:pszData withSize:aSize withOffSet:pos nalUnit: &nalu]))
    {
        if(nalu.type==0x07)//SPS
        {
            if(nalu.size>0){
                memcpy(m_avcCBox.spsBuffer, nalu.data, nalu.size);
                m_avcCBox.sps_length=nalu.size;
            }
            firstIFrame_states = 1;
        }
        
        if(nalu.type==0x08)//PPS
        {
            if(nalu.size>0){
                memcpy(m_avcCBox.ppsBuffer, nalu.data, nalu.size);
                m_avcCBox.pps_length=nalu.size;
            }
        }
        
        if(firstIFrame_states!=1){
            printf("NO_SPS_RETURN: \n");//獲取第一個(gè)I幀. 否則就直接返回.
            startTimeStamp=[self _GetTickCount];
            pos += len;
            continue ;
        }
        
        if(nalu.type == 0x05) //i幀
        {
            FrameData *pFrameData=(FrameData *)malloc(sizeof(FrameData));
            memset(pFrameData, 0, sizeof(FrameData));
            
            int datalen = nalu.size+4;
            unsigned char *pData =(unsigned char *) malloc( datalen * sizeof(unsigned char));
            // MP4 Nalu前四個(gè)字節(jié)表示Nalu長度
            pData[0] = nalu.size>>24;
            pData[1] = nalu.size>>16;
            pData[2] = nalu.size>>8;
            pData[3] = nalu.size&0xff;
            memcpy(pData+4,nalu.data,nalu.size);
            
            
            
            
            pFrameData->m_nSize = datalen;
            memcpy(pFrameData->m_pszData, pData, datalen);
            
            
            
            pFrameData->m_nCodecID = CODEC_ID_H264;
            pFrameData->m_nTimestamp = [self _GetTickCount]-startTimeStamp;
            
            
            [self WriteVideoSt:pFrameData withKeyFlags:1];
            
            free(pFrameData);
            
            free(pData);
            
        }
        else if(nalu.type == 0x01)//
        {
            FrameData *pFrameData=(FrameData *)malloc(sizeof(FrameData));
            memset(pFrameData, 0, sizeof(FrameData));
            
            int datalen = nalu.size+4;
            unsigned char *pData =(unsigned char *) malloc( datalen * sizeof(unsigned char));
            // MP4 Nalu前四個(gè)字節(jié)表示Nalu長度
            pData[0] = nalu.size>>24;
            pData[1] = nalu.size>>16;
            pData[2] = nalu.size>>8;
            pData[3] = nalu.size&0xff;
            memcpy(pData+4,nalu.data,nalu.size);
            
            
            
            
            pFrameData->m_nSize = datalen;
            memcpy(pFrameData->m_pszData, pData, datalen);
            
            
            
            pFrameData->m_nCodecID = CODEC_ID_H264;
            pFrameData->m_nTimestamp = [self _GetTickCount]-startTimeStamp;
            
            
            [self WriteVideoSt:pFrameData withKeyFlags:0];
            
            free(pFrameData);
            
            free(pData);
            
        }
        pos += len;
        
    }
    
    
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缎谷,一起剝皮案震驚了整個(gè)濱河市井濒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌列林,老刑警劉巖瑞你,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異席纽,居然都是意外死亡捏悬,警方通過查閱死者的電腦和手機(jī)撞蚕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門润梯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人甥厦,你說我怎么就攤上這事纺铭。” “怎么了刀疙?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵舶赔,是天一觀的道長。 經(jīng)常有香客問我谦秧,道長竟纳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任疚鲤,我火速辦了婚禮锥累,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘集歇。我一直安慰自己桶略,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布诲宇。 她就那樣靜靜地躺著际歼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姑蓝。 梳的紋絲不亂的頭發(fā)上鹅心,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機(jī)與錄音纺荧,去河邊找鬼旭愧。 笑死溯泣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的榕茧。 我是一名探鬼主播垃沦,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼用押!你這毒婦竟也來了肢簿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蜻拨,失蹤者是張志新(化名)和其女友劉穎池充,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缎讼,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡收夸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了血崭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卧惜。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖夹纫,靈堂內(nèi)的尸體忽然破棺而出咽瓷,到底是詐尸還是另有隱情,我是刑警寧澤舰讹,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布茅姜,位于F島的核電站,受9級特大地震影響月匣,放射性物質(zhì)發(fā)生泄漏钻洒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一锄开、第九天 我趴在偏房一處隱蔽的房頂上張望素标。 院中可真熱鬧,春花似錦院刁、人聲如沸糯钙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽任岸。三九已至,卻和暖如春狡刘,著一層夾襖步出監(jiān)牢的瞬間享潜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工嗅蔬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剑按,地道東北人疾就。 一個(gè)月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像艺蝴,于是被迫代替她去往敵國和親猬腰。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容