FFmpeg5开发入门教程29:多路视频合并推流

甲方爸爸要求把四路视频合并为一路视频,然后压缩推RTPS流

FFmpeg4入门27:捕获摄像头编码h264并推流中摄像头数据就是YUV原始数据,然后编码为H264,接着推流。

我们在此基础上将YUV数据合并然后编码推流。

合并

合并函数流程图为

merge

先采集摄像头数据,我的戴尔G5511笔记本默认摄像头采集输出的是

1
2
3
4
5
6
$ ffprobe /dev/video0                                       
...
Input #0, video4linux2,v4l2, from '/dev/video0':
Duration: N/A, start: 50691.224651, bitrate: 147456 kb/s
Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 1280x720, 147456 kb/s, 10 fps, 10 tbr, 1000k tbn
...

为了方便处理,我们把yuyv422转换为yuv420p(i420)

转码的过程就是之前的过程。

多路合并,即将原始数据复制一份,两路并排显示。

那么我们需要一个缓冲区来存储数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
AVFrame *dstFrame = av_frame_alloc();
int numBytes2 = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
inCodecCtx->width*2,
inCodecCtx->height,1);
uint8_t* out_buffer2 = (unsigned char*)av_malloc(numBytes2*sizeof(unsigned char));

ret = av_image_fill_arrays(dstFrame->data,
dstFrame->linesize,
out_buffer2,
AV_PIX_FMT_YUV420P,
inCodecCtx->width*2,
inCodecCtx->height,
1);
if(ret<0){
printf("Fill arrays failed.\n");
return -1;
}
dstFrame->format = inCodecCtx->pix_fmt;
dstFrame->width = inCodecCtx->width *2;
dstFrame->height = inCodecCtx->height;

//set dstframe bg to black
memset(dstFrame->data[0],0,inCodecCtx->width * inCodecCtx->height*2);
memset(dstFrame->data[1],0x80,inCodecCtx->width *inCodecCtx->height/2);
memset(dstFrame->data[2],0x80,inCodecCtx->width * inCodecCtx->height/2);

解码后的视频我们要将其合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int frameheight = inCodecCtx->height;
int framewidth = inCodecCtx->width;
printf("w -> %d h-> %d.\n",framewidth,frameheight);

if(yuvFrame){
int nYIndex =0;
int nUVIndex = 0;

for(int i=0;i<frameheight;i++){
//y
memcpy(dstFrame->data[0] + i*framewidth *2,yuvFrame->data[0]+nYIndex*framewidth,framewidth);
memcpy(dstFrame->data[0] + framewidth + i * framewidth *2,yuvFrame->data[0]+nYIndex*framewidth,framewidth);
nYIndex++;
}

for(int i=0;i<frameheight/4;i++){
//u
memcpy(dstFrame->data[1]+i*framewidth*2,yuvFrame->data[1]+nUVIndex*framewidth,framewidth);
memcpy(dstFrame->data[1]+framewidth+i*framewidth*2,yuvFrame->data[1]+nUVIndex*framewidth,framewidth);

//v
memcpy(dstFrame->data[2]+i*framewidth*2,yuvFrame->data[2]+nUVIndex*framewidth,framewidth);
memcpy(dstFrame->data[2]+framewidth+i*framewidth*2,yuvFrame->data[2]+nUVIndex*framewidth,framewidth);

nUVIndex++;
}
}

然后将缓冲区的数据保存,以便验证是否正确。

1
2
3
4
5
FILE *fp_yuv420 = fopen("test.yuv","wb+");

fwrite(dstFrame->data[0], 1, framewidth*frameheight * 2, fp_yuv420);
fwrite(dstFrame->data[1], 1, framewidth*frameheight / 2, fp_yuv420);
fwrite(dstFrame->data[2], 1, framewidth*frameheight / 2, fp_yuv420);

保存50帧进行测试

1
2
3
4
5
6
7
$ ffplay -pixel_format yuv420p -video_size 2560x720 test.yuv
...
[rawvideo @ 0x7f3bac000c80] Estimating duration from bitrate, this may be inaccurate
Input #0, rawvideo, from 'test.yuv':
Duration: 00:00:02.00, start: 0.000000, bitrate: 552960 kb/s
Stream #0:0: Video: rawvideo (I420 / 0x30323449), yuv420p, 2560x720, 552960 kb/s, 25 tbr, 25 tbn
5.45 M-V: 0.000 fd= 0 aq= 0KB vq= 0KB sq= 0B f=0/0

因为是两路合并为一路,所以结果文件的宽度(2560)是原始的两倍(1280)

效果为

yuv420p

编码推流

确认两路合并正确后,接下来我们将其编码为H264,毕竟现在没有谁会直接使用yuv格式文件。

函数流为

rtsp

确认编码无误后,我们将其推流出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
///////////////编解码部分//////////////////////
ret = avformat_write_header(outFmtCtx,NULL);

int64_t startTime = av_gettime();

while(av_read_frame(inFmtCtx,inPkt)>=0){
if(inPkt->stream_index == inVideoStreamIndex){
if(avcodec_send_packet(inCodecCtx,inPkt)>=0){
while((ret=avcodec_receive_frame(inCodecCtx,srcFrame))>=0){
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return -1;
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
exit(1);
}
sws_scale(img_ctx,
(const uint8_t* const*)srcFrame->data,
srcFrame->linesize,
0,inCodecCtx->height,
yuvFrame->data,yuvFrame->linesize);

int frameheight = inCodecCtx->height;
int framewidth = inCodecCtx->width;

if(yuvFrame){
int nYIndex =0;
int nUVIndex = 0;

for(int i=0;i<frameheight;i++){
//y
memcpy(dstFrame->data[0] + i*framewidth *2,yuvFrame->data[0]+nYIndex*framewidth,framewidth);
memcpy(dstFrame->data[0] + framewidth + i * framewidth *2,yuvFrame->data[0]+nYIndex*framewidth,framewidth);
nYIndex++;
}

for(int i=0;i<frameheight/4;i++){
//u
memcpy(dstFrame->data[1]+i*framewidth*2,yuvFrame->data[1]+nUVIndex*framewidth,framewidth);
memcpy(dstFrame->data[1]+framewidth+i*framewidth*2,yuvFrame->data[1]+nUVIndex*framewidth,framewidth);

//v
memcpy(dstFrame->data[2]+i*framewidth*2,yuvFrame->data[2]+nUVIndex*framewidth,framewidth);
memcpy(dstFrame->data[2]+framewidth+i*framewidth*2,yuvFrame->data[2]+nUVIndex*framewidth,framewidth);

nUVIndex++;
}
}

dstFrame->pts=srcFrame->pts;
//encode
if(avcodec_send_frame(outCodecCtx,dstFrame)>=0){
if(avcodec_receive_packet(outCodecCtx,outPkt)>=0){
printf("encoded one frame.\n");
//delay

av_packet_rescale_ts(outPkt,
inFmtCtx->streams[inVideoStreamIndex]->time_base,
outFmtCtx->streams[0]->time_base);
outPkt->pos=-1;
av_interleaved_write_frame(outFmtCtx,outPkt);
av_packet_unref(outPkt);
}
}
//usleep(1000*24);
}
}
av_packet_unref(inPkt);
}
}

av_write_trailer(outFmtCtx);
////////////////编解码部分结束////////////////

效果为

rtsp

完整代码在ffmpeg_Beginner中的29.1video_merge_yuv29.2video_merge_yuv2h264中。


FFmpeg5开发入门教程29:多路视频合并推流
https://feater.top/ffmpeg/ffmpeg-merge-yuv-and-push
作者
JackeyLea
发布于
2022年7月12日
许可协议