網(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;
}
}