由于我的程序,只针对个人需求,所以输入文件仅限于通过ffmpeg转码出来的视频。
ffmpeg默认转出来的sei的tpye是5, 且读取packet的data时发现只在第一个关键帧前面有sei.
#include
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include
#include
#include
#ifdef __cplusplus
};
#endif
#endif
void PrintBuffer(void* pBuff, unsigned int nLen)
{
if (NULL == pBuff || 0 == nLen)
{
return;
}
const int nBytePerLine = 16;
unsigned char* p = (unsigned char*)pBuff;
char szHex[49] = { 0 };
printf("-----------------begin-------------------\n");
unsigned int i = 0;
for (i = 0; i < nLen; ++i)
{
int idx = 3 * (i % nBytePerLine);
if (0 == idx)
{
memset(szHex, 0, sizeof(szHex));
}
#ifdef WIN32
sprintf_s(&szHex[idx], 4, "%02x ", p[i]);// buff长度要多传入1个字节
#else
snprintf(&szHex[idx], 4, "%02x ", p[i]); // buff长度要多传入1个字节
#endif
// 以16个字节为一行,进行打印
if (0 == ((i + 1) % nBytePerLine))
{
printf("%s\n", szHex);
}
}
// 打印最后一行未满16个字节的内容
if (0 != (nLen % nBytePerLine))
{
printf("%s\n", szHex);
}
printf("------------------end-------------------\n");
}
//大端转小端
uint32_t reversebytes(uint32_t value) {
return (value & 0x000000FFU) << 24 | (value & 0x0000FF00U) << 8 |
(value & 0x00FF0000U) >> 8 | (value & 0xFF000000U) >> 24;
}
void put_sei_time(uint8_t** sei_data){
uint8_t* pdata = *sei_data;
time_t t = time(NULL);
tm *tp = localtime(&t);
// 北京时间
printf("\n\nPut %d/%d/%d %d:%d:%d\n", tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
*(pdata + 7) = tp->tm_hour;
*(pdata + 8) = tp->tm_min;
*(pdata + 9) = tp->tm_sec;
}
int main2(int argc, char* argv[])
{
AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
const char *in_filename, *out_filename;
int ret, i;
int videoindex = -1;
int frame_index = 0;
int64_t start_time = 0;
in_filename = "tcd-g2.flv";//输入URL(Input file URL)
out_filename = "tcd-g2-sei.flv";
av_register_all();
avformat_network_init();
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
printf("Could not open input file.");
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
printf("Failed to retrieve input stream information");
goto end;
}
for (i = 0; inb_streams; i++)
if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
videoindex = i;
break;
}
av_dump_format(ifmt_ctx, 0, in_filename, 0);
avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename); //RTMP
if (!ofmt_ctx) {
printf("Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ofmt = ofmt_ctx->oformat;
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
if (!out_stream) {
printf("Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0) {
printf("Failed to copy context from input to output stream codec context\n");
goto end;
}
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
av_dump_format(ofmt_ctx, 0, out_filename, 1);
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
printf("Could not open output URL '%s'", out_filename);
goto end;
}
}
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
printf("Error occurred when opening output URL\n");
goto end;
}
start_time = av_gettime();
uint8_t* sei_data = (uint8_t*)malloc(4 + 250);
memset(sei_data, 0, 254);
while (1) {
AVStream *in_stream, *out_stream;
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
if (pkt.stream_index == videoindex){
frame_index++;
if (frame_index>750){
av_free_packet(&pkt);
break;
}
if (pkt.flags & AV_PKT_FLAG_KEY) // is keyframe
{
//Pkt.size是纯data的size+4字节起始码
printf("frame_index:%d pktsize:%d\n",frame_index,pkt.size);
if (*(sei_data + 3) == 0){
*sei_data = 0;
*(sei_data + 1) = 0;
*(sei_data + 2) = 0;
*(sei_data + 3) = 250;
*(sei_data + 3) = 250;
*(sei_data + 4) = 6;
*(sei_data + 5) = 0x64;
*(sei_data + 6) = 250 - 4;
*(sei_data + 253) = 0x80;
PrintBuffer(sei_data, 254);
}
if ((pkt.data[4] & 0x1F) == 6){//判断是否是SEI
uint32_t sei_nal_size = 0;
memcpy(&sei_nal_size, pkt.data, 4);
sei_nal_size = reversebytes(sei_nal_size);
printf("sei_nal_size:%d\n",sei_nal_size);
uint8_t* tmpdata = (uint8_t*)malloc(254 + pkt.size - 4 - sei_nal_size);
put_sei_time(&sei_data);
memcpy(tmpdata, sei_data, 254);
memcpy(tmpdata + 254, pkt.data + 4 + sei_nal_size, pkt.size - 4 - sei_nal_size);
pkt.data = tmpdata;
pkt.size = 254 + pkt.size - 4 - sei_nal_size;
}
else{
uint8_t* tmpdata = (uint8_t*)malloc(254 + pkt.size);
put_sei_time(&sei_data);
memcpy(tmpdata, sei_data, 254);
memcpy(tmpdata + 254, pkt.data, pkt.size);
pkt.data = tmpdata;
pkt.size = 254 + pkt.size;
}
PrintBuffer(pkt.data, 300);
printf("%d\n",pkt.size);
}
AVRational time_base = ifmt_ctx->streams[videoindex]->time_base;
AVRational time_base_q = { 1, AV_TIME_BASE };
int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
int64_t now_time = av_gettime() - start_time;
if (pts_time > now_time)
av_usleep(pts_time - now_time);
}
else{
if (frame_index == 0){
av_free_packet(&pkt);
continue;
}
}
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
printf("Error muxing packet\n");
break;
}
av_free_packet(&pkt);
}
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
if (ret < 0 && ret != AVERROR_EOF) {
printf("Error occurred.\n");
return -1;
}
printf("end called\n");
return getchar();
}
int main(int argc, char* argv[])
{
AVFormatContext *ifmt_ctx = NULL;
AVPacket pkt;
const char *in_filename;
int ret, i;
int videoindex = -1;
int frame_index = 0;
int64_t start_time = 0;
in_filename = "tcd-g2-sei.flv";
av_register_all();
avformat_network_init();
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
printf("Could not open input file.");
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
printf("Failed to retrieve input stream information");
goto end;
}
for (i = 0; inb_streams; i++)
if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
videoindex = i;
break;
}
av_dump_format(ifmt_ctx, 0, in_filename, 0);
start_time = av_gettime();
while (1) {
AVStream *in_stream;
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0){
printf("read finished!\n");
break;
}
if (pkt.stream_index == videoindex){
frame_index++;
if (pkt.flags & AV_PKT_FLAG_KEY) // is keyframe
{
time_t t = time(NULL);
tm *tp = localtime(&t);
// 北京时间
printf("\n\nCurrent %d/%d/%d %d:%d:%d\n", tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
//Pkt.size是纯data的size+4字节起始码
printf("frame_index:%d pktsize:%d\n", frame_index, pkt.size);
//PrintBuffer(pkt.data, 64);
uint8_t* pdata = pkt.data;
PrintBuffer(pkt.data, 64);
printf("Get %d:%d:%d\n", *(pdata + 7), *(pdata + 8), *(pdata + 9));
printf("Calculate Delay : %d\n", (tp->tm_min*60+ tp->tm_sec)-(*(pdata + 8) * 60 + *(pdata + 9)));
}
AVRational time_base = ifmt_ctx->streams[videoindex]->time_base;
AVRational time_base_q = { 1, AV_TIME_BASE };
int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
int64_t now_time = av_gettime() - start_time;
if (pts_time > now_time)
av_usleep(pts_time - now_time);
}
av_free_packet(&pkt);
}
end:
avformat_close_input(&ifmt_ctx);
if (ret < 0 && ret != AVERROR_EOF) {
printf("Error occurred.\n");
return -1;
}
return getchar();
}
我的需求很简单,要写到sei中的数据很少,所以payloadsize只用一个字节表示,即理论上最多254个字节的sei数据。
上面程序中固定使用sei的payloadtype是0x64 (100),
payloadsize是0xf6 (246)
每个sei的payload data前三个字节,分别写入了北京时间的时,分,秒。
通过推流端在sei写入实时北京时间,然后另一端程序去读sei,对比当前北京时间,则计算出精准延迟。