拉流

网络摄像头拉流

1.接收视频数据

1.1结构体

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这个结构标识一个分数,num为分数,den为分母。

实际上time_base的意思就是时间的刻度:
如(1,25),那么时间刻度就是1/25; (1,90000),那么时间刻度就是1/90000
那么,在刻度为1/25的体系下的time=5,转换成在刻度为1/90000体系下的时间time为(51/25)/(1/90000) = 36005=18000

ffmpeg中做pts计算时,存在大量这种转换

在以下结构中都有
AVCodecContext:编解码上下文。
AVStream:文件或其它容器中的某一个track。
如果由某个解码器产生固定帧率的码流
AVCodecContext中的AVRational根据帧率来设定,如25帧,那么num = 1,den=25
AVStream中的time_base一般根据其采样频率设定,如(1,90000)
在某些场景下涉及到PTS的计算时,就涉及到两个Time的转换,以及到底取哪里的time_base进行转换:

场景1:编码器产生的帧,直接存入某个容器的AVStream中,那么此时packet的Time要从AVCodecContext的time转换成目标AVStream的time
场景2:从一种容器中demux出来的源AVStream的frame,存入另一个容器中某个目的AVStream。此时的时间刻度应该从源AVStream的time,转换成目的AVStream timebase下的时间。
其实,问题的关键还是要理解,不同的场景下取到的数据帧的time是相对哪个时间体系的。
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;          //当前位置的pts pts presentation time stamp 度量解码后的视频帧什么时候被显示出来
    double m_dCurTime;      //当前位置的时间(没有使用)
    uint m_nTotalTime;      //总时长
    uint m_nLastTimestamp;  //上一个位置的时间戳
}RecordInfo;

1.1.4 时间计时器
//时间计时器

unsigned int _getTickCount() {
    
    struct timeval tv;
    //gettimeofday 获取当前精确时间
    /*
     int gettimeofday(struct timeval*tv, struct timezone *tz);
     其参数tv是保存获取时间结果的结构体,参数tz用于保存时区结果:
     struct timezone{
     int tz_minuteswest;//格林威治时间往西方的时差
    int tz_dsttime;DST //时间的修正方式
}
timezone 参数若不使用则传入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;             //录像状态
    
    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");//获取第一个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前四个字节表示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前四个字节表示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;
        
    }
    
    
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容