最近做的一个项目,需要对接海康摄像头传过来的视频数据,经过分析知道传过来的是PS封装后的数据,不能直接播放,需要把PS流解析成h264裸流。经过研究有两种方法:1.直接扔给ffmpeg解析,这种方法需要用AVIOContext自定义输入;2.自己解析PS流,这种方法比较直接,只要熟悉PS解析的流程就可以做。我两种方法都实现了,刚开始是用ffmpeg,后来发现因为需要开线程,异步读写,复杂性较高,最主要是因为出现一个内存一直上涨的问题一直没解决,然后想着能不能自己解析PS,于是网上找了很多资料,终于也实现了,自己解析PS就是比较简单,不用开线程,不用异步处理,只要调用一个算法就能得出解析结果。这里记录下两种方法的实现,希望对后来者有所帮助。
一、采用ffmpeg解析PS流
首先需要用AVIOContext自定义输入,我封装为一个H264Reader类,如下:
#pragma once
#include <stdio.h>
#include <thread>
extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#include "rexkit\BlockingRingBuffer.h"
#include "FacdNetMediaServer.h"
class H264Reader
{
public:
H264Reader();
int Run(DevStreamHandle* aStreamHandle);
virtual ~H264Reader();
void Stop() {
this->running = false;
}
private:
AVFormatContext *ic = NULL;
AVIOContext *avio = NULL;
unsigned char *avio_ctx_buffer = NULL;
public:
bool running = true;
DevStreamHandle* streamHandle;
};
#include "H264Reader.h"
#include <iostream>
#include "liblogger\liblogger.h"
using namespace std;
static void XSleep(int ms)
{
//c++ 11
chrono::milliseconds du(ms);
this_thread::sleep_for(du);
}
H264Reader::H264Reader()
{
}
static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
H264Reader *h264 = (H264Reader*)opaque;
DevStreamHandle *handle = h264->streamHandle;
En_BlockingRingBuffer_FetchResult ret =handle->ringBuff->Fetch(buf, buf_size); //读取环形缓冲区的数据
if (ret == RBFR_OK)
return buf_size;
else {
return 0;
}
}
int H264Reader::Run(DevStreamHandle* stream_handle)
{
streamHandle = stream_handle;
int isopen = -1;
size_t avio_ctx_buffer_size = 1024*32;//
ic = avformat_alloc_context();
avio_ctx_buffer = (unsigned char *)av_malloc(avio_ctx_buffer_size);
printf("H264Reader::Run av_malloc()\n");
avio = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, read_packet, NULL, NULL);
ic->pb = avio;
isopen = avformat_open_input(&ic, NULL, NULL, NULL);
if (isopen < 0) {
LogError("Could not open input----->>>\n");
return -1;
}
AVPacket *pkt = av_packet_alloc();
while (running)
{
int re = av_read_frame(ic, pkt);
if (re != 0){
av_packet_unref(pkt);
printf("~~~~~~~~~av_read_frame()= %d\n", re);
continue;
}
g_MainMediaServer.PostDataToRtsp((char*)pkt->data, pkt->size, stream_handle);
av_packet_unref(pkt);
}
if (avio) {
av_freep(&avio->buffer);
av_freep(&avio);
}
return 0;
}
H264Reader::~H264Reader()
{
//av_free(avio_ctx_buffer);
}
使用H264Reader类需要开启一个线程,在线程里调用H264Reader的Run()函数。使用AVIOContext,需要定义一个读回调函数,上述代码 avio = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, read_packet, NULL, NULL);这行就是把自定义回调函数read_packet绑定到AVIOContext,其中第四个参数是用户数据,这里传入this,可以在回调里面获取这个用户数据。在回调里面给ffmpeg写入PS流,ffmpeg内部会不断调用这个回调,然后在循环里面不断调用 av_read_frame(ic, pkt),返回的AVPacket变量pkt就是已经被ffmpeg解析后的h264裸流。
使用AVIOContext自定义输入的难点在于需要设计一个缓冲区,写入线程需要向缓冲区写入不定大小的数据,而读取线程需要向缓冲区读取不定大小的数据,因为写入和读取的大小都不固定所以不能定义一个固定大小的缓冲区,这里我采用一个环形阻塞缓冲链表BlockingRingBuffer。
在写入线程需要调用BlockingRingBuffer的Cat()函数写入PS流:
// 取到PS流,存储在ringBuff
int FacdNetMediaServer::cbPreStreamMedia(HSTREAM hStream, const StreamMediaFrame *cFrame, DWORD dwUserData)
{
DevStreamHandle *pHandle = (DevStreamHandle*)dwUserData;
if (pHandle != NULL && pHandle->ringBuff != NULL){
pHandle->ringBuff->Cat((const unsigned char*)cFrame->cFrameBuffer.pBuffer, cFrame->cFrameBuffer.dwBufLen);
}
return 0;
}
在AVIOContext的读回调里面调用BlockingRingBuffer的Fecth()函数读取PS流:
static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
H264Reader *h264 = (H264Reader*)opaque;
DevStreamHandle *handle = h264->streamHandle;
En_BlockingRingBuffer_FetchResult ret =handle->ringBuff->Fetch(buf, buf_size); //
if (ret == RBFR_OK)
return buf_size;
else {
return 0;
}
}
二,采用自己解析PS流的方法
这个方法就简单多了,主要思想是两个PS流头部之间的包拼接成一个数据包扔给PS解析函数处理,得出的就是一个h264帧,PS头以0x000001ba标识,所以拼接方法如下:
int FacdNetMediaServer::cbPreStreamMedia(HSTREAM hStream, const StreamMediaFrame *cFrame, DWORD dwUserData)
{
DevStreamHandle *pHandle = (DevStreamHandle*)dwUserData;
time_t t;
t = time(NULL);
pHandle->pusher_time = time(&t);
if (pHandle != NULL && pHandle->cam != NULL){
char *h264Frame;
int h264Len;
char* buffer = (char*)cFrame->cFrameBuffer.pBuffer;
int len = cFrame->cFrameBuffer.dwBufLen;
//printf("%02x %02x %02x %02x\n", buffer[0], buffer[1], buffer[2], buffer[3]);
//查找ps头 0x000001BA
if (buffer[0] == '\x00' && buffer[1] == '\x00' && buffer[2] == '\x01' && buffer[3] == '\xba')
{
if (!pHandle->isfirst)
{
//此包为ps新的一帧,每次到这里都先处理存储好的前一帧
GetH246FromPs(pHandle->h264_pkt, pHandle->pkt_len, &h264Frame, &h264Len);
//海康流特殊处理部分:分界符数据(nal_unit_type=9)或补充增强信息单元(nal_unit_type=6),如果直接送入解码器,有可能会出现问题,直接舍弃.00 00 01 bd和 00 00 01 c0为私有标志和音频数据舍弃
if (h264Frame[0] >> 5 == '\x06' || h264Frame[0] >> 5 == '\x09' || h264Frame[0] >> 5 == '\x0a' || h264Frame[0] >> 5 == '\x0b' || h264Frame[0] >> 5 == '\x0c')
{
}
else {//这就是获取的含有标准正常H264数据的帧 我们开始进行处理,将该帧存入Deque,该deque只负责存储所有的一帧帧的数据
bool ret = pHandle->cam->deliverFrame(h264Frame, h264Len);
if (ret) {
time_t t1;
t1 = time(NULL);
pHandle->puller_time = time(&t1);
}
}
}
//各个变量初始化,开始拼接下一个帧(可能收到的N个包才能组成一个帧,所以这里有一个拼接操作,主要是一个帧的头部指针一直memcpy,把内存向后叠加)
pHandle->pkt_len = 0;
memset(pHandle->h264_pkt, 0, MAX_FRAME_SIZE);
memcpy(pHandle->h264_pkt + pHandle->pkt_len, buffer, len);
pHandle->pkt_len += len;
pHandle->isfirst = false;
}
else {//当然如果开头不是0x000001BA,默认为一个帧的中间部分,我们将这部分内存顺着帧的开头向后存储
if (!pHandle->isfirst)
{
//排除音频和私有数据
if (buffer[0] == '\x00' && buffer[1] == '\x00' && buffer[2] == '\x01' && (buffer[3] == '\xc0' || buffer[3] == '\xbd'))
{
return 0;
}
//这是正常的帧数据
if (pHandle->pkt_len + len > MAX_FRAME_SIZE) {
LogError("帧大小超过5MB");
return -1;
}
memcpy(pHandle->h264_pkt + pHandle->pkt_len, buffer, len);
pHandle->pkt_len += len;
}
}
}
return 0;
}
GetH246FromPs()就是解析PS流的接口,定义如下:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma pack(1)
union littel_endian_size
{
unsigned short int length;
unsigned char byte[2];
};
struct pack_start_code
{
unsigned char start_code[3];
unsigned char stream_id[1];
};
struct program_stream_pack_header
{
pack_start_code PackStart;// 4
unsigned char Buf[9];
unsigned char stuffinglen;
};
struct program_stream_map
{
pack_start_code PackStart;
littel_endian_size PackLength;//we mast do exchange
//program_stream_info_length
//info
//elementary_stream_map_length
//elem
};
struct program_stream_e
{
pack_start_code PackStart;
littel_endian_size PackLength;//we mast do exchange
char PackInfo1[2];
unsigned char stuffing_length;
};
#pragma pack()
int inline ProgramStreamPackHeader(char* Pack, int length, char **NextPack, int *leftlength)
{
//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
//通过 00 00 01 ba头的第14个字节的最后3位来确定头部填充了多少字节
program_stream_pack_header *PsHead = (program_stream_pack_header *)Pack;
unsigned char pack_stuffing_length = PsHead->stuffinglen & '\x07';
*leftlength = length - sizeof(program_stream_pack_header) - pack_stuffing_length;//减去头和填充的字节
*NextPack = Pack + sizeof(program_stream_pack_header) + pack_stuffing_length;
if (*leftlength<4) return 0;
//printf("[%s]2 %x %x %x %x\n", __FUNCTION__, (*NextPack)[0], (*NextPack)[1], (*NextPack)[2], (*NextPack)[3]);
return *leftlength;
}
inline int ProgramStreamMap(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
{
//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
program_stream_map* PSMPack = (program_stream_map*)Pack;
//no payload
*PayloadData = 0;
*PayloadDataLen = 0;
if (length < sizeof(program_stream_map)) return 0;
littel_endian_size psm_length;
psm_length.byte[0] = PSMPack->PackLength.byte[1];
psm_length.byte[1] = PSMPack->PackLength.byte[0];
*leftlength = length - psm_length.length - sizeof(program_stream_map);
//printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);
if (*leftlength <= 0) return 0;
*NextPack = Pack + psm_length.length + sizeof(program_stream_map);
return *leftlength;
}
inline int Pes(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
{
//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
program_stream_e* PSEPack = (program_stream_e*)Pack;
*PayloadData = 0;
*PayloadDataLen = 0;
if (length < sizeof(program_stream_e)) return 0;
littel_endian_size pse_length;
pse_length.byte[0] = PSEPack->PackLength.byte[1];
pse_length.byte[1] = PSEPack->PackLength.byte[0];
if (pse_length.length + 6 > length)
{
pse_length.length = length - 6;
/*int PES_packet_length_fixed = length - 6;
Pack[4] = (char)((PES_packet_length_fixed && 0xFF00) >> 8);
Pack[5] = (char)(PES_packet_length_fixed & 0x00FF);*/
}
*PayloadDataLen = pse_length.length - 2 - 1 - PSEPack->stuffing_length;
if (*PayloadDataLen>0)
*PayloadData = Pack + sizeof(program_stream_e) + PSEPack->stuffing_length;
*leftlength = length - pse_length.length - sizeof(pack_start_code) - sizeof(littel_endian_size);
//printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);
if (*leftlength <= 0) return 0;
*NextPack = Pack + sizeof(pack_start_code) + sizeof(littel_endian_size) + pse_length.length;
return *leftlength;
}
int inline GetH246FromPs(char* buffer, int length, char **h264Buffer, int *h264length)
{
int leftlength = 0;
char *NextPack = buffer;
*h264Buffer = buffer;
*h264length = 0;
leftlength = length;
if (ProgramStreamPackHeader(buffer, length, &NextPack, &leftlength) == 0)
return 0;
char *PayloadData = NULL;
int PayloadDataLen = 0;
while (leftlength >= sizeof(pack_start_code))
{
PayloadData = NULL;
PayloadDataLen = 0;
if (NextPack
&& NextPack[0] == '\x00'
&& NextPack[1] == '\x00'
&& NextPack[2] == '\x01'
&& NextPack[3] == '\xE0')
{
//接着就是流包,说明是非i帧
if (Pes(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen))
{
if (PayloadDataLen)
{
memcpy(buffer, PayloadData, PayloadDataLen);
buffer += PayloadDataLen;
*h264length += PayloadDataLen;
}
}
else
{
if (PayloadDataLen)
{
memcpy(buffer, PayloadData, PayloadDataLen);
buffer += PayloadDataLen;
*h264length += PayloadDataLen;
}
break;
}
}
else if (NextPack
&& NextPack[0] == '\x00'
&& NextPack[1] == '\x00'
&& NextPack[2] == '\x01'
&& NextPack[3] == '\xBC')
{
if (ProgramStreamMap(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen) == 0)
continue;
}
else
{
//printf("no konw %x %x %x %x\n", NextPack[0], NextPack[1], NextPack[2], NextPack[3]);
break;
}
}
return *h264length;
}