FFmpeg4入门系列教程26:捕获摄像头编码h264并推流

索引地址:系列教程索引地址

上一篇:FFmpeg4入门系列教程25:本地文件推流

本文内容包括:

  • 获取摄像头数据
  • 压缩摄像头视频为H264
  • H264数据推流

FFmpeg4入门系列教程14:Linux下摄像头捕获并编码为h264中包括获取和压缩的过程,FFmpeg4入门系列教程25:本地文件推流包含数据推流的过程。

那么就是怎么把这两个合并。

先看一下流程图

flow

首先打开输入文件,摄像头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1.1 打开输入文件,获取封装格式相关信息
av_dict_set_int(&options, "rtbufsize", 18432000 , 0);
if ((ret = avformat_open_input(&ifmtCtx, inFilename, ifmt, &options)) < 0)
{
printf("can't open input file: %s\n", inFilename);
goto end;
}

// 1.2 解码一段数据,获取流相关信息
if ((ret = avformat_find_stream_info(ifmtCtx, 0)) < 0)
{
printf("failed to retrieve input stream information\n");
goto end;
}

输入解码器部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1.4 查找输入解码器
pCodec = avcodec_find_decoder(ifmtCtx->streams[videoIndex]->codecpar->codec_id);
if (!pCodec)
{
printf("can't find codec\n");
goto end;
}

pCodecCtx = avcodec_alloc_context3(pCodec);
if (!pCodecCtx)
{
printf("can't alloc codec context\n");
goto end;
}

avcodec_parameters_to_context(pCodecCtx, ifmtCtx->streams[videoIndex]->codecpar);

// 1.5 打开输入解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
printf("can't open codec\n");
goto end;
}

输入的摄像头数据要不同编码为H264

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
// 1.6 查找H264编码器
pH264Codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!pH264Codec)
{
printf("can't find h264 codec.\n");
goto end;
}

// 1.6.1 设置参数
pH264CodecCtx = avcodec_alloc_context3(pH264Codec);
pH264CodecCtx->codec_id = AV_CODEC_ID_H264;
pH264CodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pH264CodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
pH264CodecCtx->width = pCodecCtx->width;
pH264CodecCtx->height = pCodecCtx->height;
pH264CodecCtx->time_base.num = 1;
pH264CodecCtx->time_base.den = 25; //帧率(即一秒钟多少张图片)
pH264CodecCtx->bit_rate = 400000; //比特率(调节这个大小可以改变编码后视频的质量)
pH264CodecCtx->gop_size = 250;
pH264CodecCtx->qmin = 10;
pH264CodecCtx->qmax = 51;
//some formats want stream headers to be separate
// if (pH264CodecCtx->flags & AVFMT_GLOBALHEADER)
{
pH264CodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}

// 1.7 打开H.264编码器
av_dict_set(&params, "preset", "superfast", 0);
av_dict_set(&params, "tune", "zerolatency", 0); //实现实时编码
if (avcodec_open2(pH264CodecCtx, pH264Codec, &params) < 0)
{
printf("can't open video encoder.\n");
goto end;
}

// 2. 打开输出
// 2.1 分配输出ctx
if (strstr(outFilename, "rtmp://"))
{
ofmtName = "flv";
}
else if (strstr(outFilename, "udp://"))
{
ofmtName = "mpegts";
}
else
{
ofmtName = NULL;
}

avformat_alloc_output_context2(&ofmtCtx, NULL, ofmtName, outFilename);
if (!ofmtCtx)
{
printf("can't create output context\n");
goto end;
}

在推流地址中添加一条流

1
2
3
4
5
6
7
8
9
10
11
12
// 2.2 创建输出流
for (i=0; i<ifmtCtx->nb_streams; ++i)
{
AVStream *outStream = avformat_new_stream(ofmtCtx, NULL);
if (!outStream)
{
printf("failed to allocate output stream\n");
goto end;
}

avcodec_parameters_from_context(outStream->codecpar, pH264CodecCtx);
}

将获取的摄像头数据编码为H264,再写入流地址中

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
while (1)
{
// 3.2 从输入流读取一个packet
ret = av_read_frame(ifmtCtx, &pkt);
if (ret < 0)
{
break;
}

if (pkt.stream_index == videoIndex)
{
ret = avcodec_send_packet(pCodecCtx, &pkt);
if (ret < 0)
{
printf("Decode error.\n");
goto end;
}

if (avcodec_receive_frame(pCodecCtx, pFrame) >= 0)
{
sws_scale(pImgConvertCtx,
(const unsigned char* const*) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data,
pFrameYUV->linesize);

pFrameYUV->format = pCodecCtx->pix_fmt;
pFrameYUV->width = pCodecCtx->width;
pFrameYUV->height = pCodecCtx->height;

ret = avcodec_send_frame(pH264CodecCtx, pFrameYUV);
if (ret < 0)
{
printf("failed to encode.\n");
goto end;
}

if (avcodec_receive_packet(pH264CodecCtx, &pkt) >= 0)
{
// 设置输出DTS,PTS
pkt.pts = pkt.dts = frameIndex * (ofmtCtx->streams[0]->time_base.den) /ofmtCtx->streams[0]->time_base.num / 25;
frameIndex++;

ret = av_interleaved_write_frame(ofmtCtx, &pkt);
if (ret < 0)
{
printf("send packet failed: %d\n", ret);
}
else
{
printf("send %5d packet successfully!\n", frameIndex);
}
}
}
}

av_packet_unref(&pkt);
}

本系列刚开始写的时候用的笔记本,自带摄像头。

现在换成台式机了,没有摄像头可以测试了。

完整代码在ffmpeg_Beginner中的26.video_encode_camera2h264_push

下一篇:FFmpeg4入门系列教程27:保存视频流数据至本地(rtsp->mp4)