Blame view

src/decoder/dvpp/FFRecoder.cpp 8.75 KB
09a835c9   Hu Chunming   修正视频截存丢帧问题
1
  // FFRecoder.cpp
09c2d08c   Hu Chunming   arm交付版
2
  #include "FFRecoder.h"
09c2d08c   Hu Chunming   arm交付版
3
4
5
  #include <tuple>
  #include <array>
  #include <vector>
09c2d08c   Hu Chunming   arm交付版
6
  
09a835c9   Hu Chunming   修正视频截存丢帧问题
7
8
9
10
11
12
13
14
15
  extern "C" {
  #include <libavcodec/avcodec.h>
  #include <libavformat/avformat.h>
  #include <libavutil/opt.h>
  #include <libavutil/timestamp.h>
  #include <libavutil/imgutils.h>
  #include <libswscale/swscale.h>
  }
  
09c2d08c   Hu Chunming   arm交付版
16
17
18
19
20
21
22
23
24
25
  
  FFRecoder::FFRecoder()
  	:width_{},
  	height_{},
  	y_size_{},
  	uv_size_{},
  	pts_{},
  	codec_ctx_{ nullptr },
  	fmt_ctx_{ nullptr },
  	out_stream_{ nullptr },
09a835c9   Hu Chunming   修正视频截存丢帧问题
26
  	yuv_frame_{ nullptr }
09c2d08c   Hu Chunming   arm交付版
27
  {
09c2d08c   Hu Chunming   arm交付版
28
29
30
31
  }
  
  FFRecoder::~FFRecoder()
  {
09a835c9   Hu Chunming   修正视频截存丢帧问题
32
  	uninit();
09c2d08c   Hu Chunming   arm交付版
33
34
35
  }
  
  
09a835c9   Hu Chunming   修正视频截存丢帧问题
36
  bool FFRecoder::init(int w, int h, int fps, int bit_rate, const char* outfile_name)
09c2d08c   Hu Chunming   arm交付版
37
38
39
40
41
42
43
44
  {
  	uninit();
  
  	width_ = w;
  	height_ = h;
  	y_size_ = w * h;
  	uv_size_ = y_size_ / 4;
  
09a835c9   Hu Chunming   修正视频截存丢帧问题
45
46
  	m_fps = fps;
  
09c2d08c   Hu Chunming   arm交付版
47
  	// [1] 创建解码器
09a835c9   Hu Chunming   修正视频截存丢帧问题
48
  	const AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
09c2d08c   Hu Chunming   arm交付版
49
  	if (!encoder) {
09a835c9   Hu Chunming   修正视频截存丢帧问题
50
  		fprintf(stderr, "Find encoder AV_CODEC_ID_H264 failed!\n");
09c2d08c   Hu Chunming   arm交付版
51
52
53
54
55
  		return false;
  	}
  	// 获取解码器上下文
  	codec_ctx_ = avcodec_alloc_context3(encoder);
  	if (!codec_ctx_) {
09a835c9   Hu Chunming   修正视频截存丢帧问题
56
  		fprintf(stderr, "Alloc context for encoder contx failed!\n");
09c2d08c   Hu Chunming   arm交付版
57
58
59
  		return false;
  	}
  	// 设置解码器上下文参数
09a835c9   Hu Chunming   修正视频截存丢帧问题
60
  	codec_ctx_->bit_rate = bit_rate;
09c2d08c   Hu Chunming   arm交付版
61
62
  	codec_ctx_->width = width_;
  	codec_ctx_->height = height_;
09a835c9   Hu Chunming   修正视频截存丢帧问题
63
64
65
  	codec_ctx_->time_base = AVRational{ 1, fps };
  	codec_ctx_->gop_size = 50;
  	codec_ctx_->max_b_frames = 0;
09c2d08c   Hu Chunming   arm交付版
66
67
  	codec_ctx_->pix_fmt = AV_PIX_FMT_YUV420P;
  	codec_ctx_->thread_count = 4;
09a835c9   Hu Chunming   修正视频截存丢帧问题
68
69
70
  	codec_ctx_->qmin = 10;
  	codec_ctx_->qmax = 51;
  	codec_ctx_->qcompress = 0.6f;
09c2d08c   Hu Chunming   arm交付版
71
72
73
74
75
76
  	codec_ctx_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  
  	av_opt_set(codec_ctx_->priv_data, "preset", "ultrafast", 0);
  	av_opt_set(codec_ctx_->priv_data, "tune", "zerolatency", 0);
  
  	// 打开解码器
b1f5dd81   Hu Chunming   注释无效代码
77
78
79
80
81
  	// int ret = avcodec_open2(codec_ctx_, encoder, nullptr);
  	// if (ret < 0) {
  	// 	fprintf(stderr, "Open encoder failed!\n");
  	// 	return false;
  	// }
09c2d08c   Hu Chunming   arm交付版
82
83
84
85
86
87
88
89
90
  
  	// [2] 创建输出上下文
  	avformat_alloc_output_context2(&fmt_ctx_, nullptr, nullptr, outfile_name);
  
  	// [3] 添加输出视频流
  	out_stream_ = avformat_new_stream(fmt_ctx_, nullptr);
  	out_stream_->id = 0;
  	out_stream_->codecpar->codec_tag = 0;
  	avcodec_parameters_from_context(out_stream_->codecpar, codec_ctx_);
09c2d08c   Hu Chunming   arm交付版
91
92
93
  
  	av_dump_format(fmt_ctx_, out_stream_->id, outfile_name, 1);
  
b1f5dd81   Hu Chunming   注释无效代码
94
95
96
97
98
99
100
101
102
103
104
105
  	// // 创建YUV格式帧
  	// yuv_frame_ = av_frame_alloc();
  	// yuv_frame_->format = AV_PIX_FMT_YUV420P;
  	// yuv_frame_->width = width_;
  	// yuv_frame_->height = height_;
  	// // 为创建的YUV帧分配内存
  	// if (av_frame_get_buffer(yuv_frame_, 0) < 0) {
  	// 	av_frame_free(&yuv_frame_);
  	// 	yuv_frame_ = nullptr;
  	// 	fprintf(stderr, "Frame get buffer failed!\n");
  	// 	return false;
  	// }
a63dd3d2   Hu Chunming   修复recode初始化崩溃问题
106
  
09c2d08c   Hu Chunming   arm交付版
107
108
  	// [5] 打开输出视频文件并写入视频头信息
  	if (avio_open(&fmt_ctx_->pb, outfile_name, AVIO_FLAG_WRITE) < 0) {
09a835c9   Hu Chunming   修正视频截存丢帧问题
109
  		fprintf(stderr, "avio_open  failed!\n");
09c2d08c   Hu Chunming   arm交付版
110
111
112
  		return false;
  	}
  	if (avformat_write_header(fmt_ctx_, nullptr) < 0) {
09a835c9   Hu Chunming   修正视频截存丢帧问题
113
  		fprintf(stderr, "Write header failed!\n");
09c2d08c   Hu Chunming   arm交付版
114
115
  		return false;
  	}
746db74c   Hu Chunming   实现recode
116
117
  
  	return true;
09c2d08c   Hu Chunming   arm交付版
118
119
  }
  
09c2d08c   Hu Chunming   arm交付版
120
121
  void FFRecoder::uninit()
  {
09c2d08c   Hu Chunming   arm交付版
122
123
124
125
126
127
128
129
130
131
132
133
134
  	if (fmt_ctx_) {
  		av_write_trailer(fmt_ctx_);
  		avio_close(fmt_ctx_->pb);
  		avformat_free_context(fmt_ctx_);
  		fmt_ctx_ = nullptr;
  	}
  
  	if (codec_ctx_) {
  		avcodec_close(codec_ctx_);
  		avcodec_free_context(&codec_ctx_);
  		codec_ctx_ = nullptr;
  	}
  
09a835c9   Hu Chunming   修正视频截存丢帧问题
135
136
137
138
139
  	if (yuv_frame_) {
  		av_frame_free(&yuv_frame_);
  		yuv_frame_ = nullptr;
  	}
  
09c2d08c   Hu Chunming   arm交付版
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
  	width_ = 0;
  	height_ = 0;
  	y_size_ = 0;
  	uv_size_ = 0;
  	pts_ = 0;
  }
  
  bool FFRecoder::write_image(const uint8_t* bgr)
  {
  	// 分配YUV格式数据的内存
  	thread_local std::vector<uint8_t> yuv_data;
  	if (yuv_data.size() != y_size_ * 3 / 2) {
  		yuv_data.resize(y_size_ * 3 / 2);
  	}
  	// BGR格式转YUV格式
  	bgr_to_yuv420p(bgr, yuv_data.data());
  
  	return write_yuv(yuv_data.data());
  }
  
  bool FFRecoder::write_yuv(const uint8_t* yuv_data)
  {
  	//拷贝YUV数据到帧,由于帧数据存在内存对齐,故需逐行拷贝
  	for (int i = 0; i < height_; i++) {
  		memcpy(yuv_frame_->data[0] + i * yuv_frame_->linesize[0], yuv_data + width_ * i, width_);
  	}
  	const int uv_stride = width_ / 2;
  	for (int i = 0; i < height_ / 2; i++) {
  		memcpy(yuv_frame_->data[1] + i * yuv_frame_->linesize[1], yuv_data + y_size_ + uv_stride * i, uv_stride);
  		memcpy(yuv_frame_->data[2] + i * yuv_frame_->linesize[2], yuv_data + y_size_ + uv_size_ + uv_stride * i, uv_stride);
  	}
  
  	yuv_frame_->pts = pts_++;
  
  	return write_frame(yuv_frame_);
  }
  
09a835c9   Hu Chunming   修正视频截存丢帧问题
177
  bool FFRecoder::write_frame(const AVFrame* frame)
09c2d08c   Hu Chunming   arm交付版
178
  {
09c2d08c   Hu Chunming   arm交付版
179
180
  	char errbuf[64]{ 0 };
  	// 将帧数据发送到编码器
09a835c9   Hu Chunming   修正视频截存丢帧问题
181
  	int ret = avcodec_send_frame(codec_ctx_, frame);
09c2d08c   Hu Chunming   arm交付版
182
  	if (ret < 0) {
09a835c9   Hu Chunming   修正视频截存丢帧问题
183
  		fprintf(stderr, "Error sending a frame to the encoder: %s\n", av_make_error_string(errbuf, sizeof(errbuf), ret));
09c2d08c   Hu Chunming   arm交付版
184
185
186
187
188
189
190
191
192
193
  		return false;
  	}
  
  	while (true) {
  		AVPacket pkt{ 0 };
  		// 获取编码后的数据
  		ret = avcodec_receive_packet(codec_ctx_, &pkt);
  		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  			return true;
  		else if (ret < 0) {
09a835c9   Hu Chunming   修正视频截存丢帧问题
194
  			fprintf(stderr, "Error encoding a frame: %s\n", av_make_error_string(errbuf, sizeof(errbuf), ret));
09c2d08c   Hu Chunming   arm交付版
195
196
197
198
199
  			return false;
  		}
  		// pts缩放到输出流的time_base
  		av_packet_rescale_ts(&pkt, codec_ctx_->time_base, out_stream_->time_base);
  		pkt.stream_index = out_stream_->index;
09c2d08c   Hu Chunming   arm交付版
200
201
  		// 将数据写入到输出流
  		ret = av_interleaved_write_frame(fmt_ctx_, &pkt);
09c2d08c   Hu Chunming   arm交付版
202
203
  		av_packet_unref(&pkt);
  		if (ret < 0) {
09a835c9   Hu Chunming   修正视频截存丢帧问题
204
  			fprintf(stderr, "Error while writing output packet: %s\n", av_make_error_string(errbuf, sizeof(errbuf), ret));
09c2d08c   Hu Chunming   arm交付版
205
206
  			return false;
  		}
09c2d08c   Hu Chunming   arm交付版
207
208
209
210
211
  	}
  
  	return true;
  }
  
09a835c9   Hu Chunming   修正视频截存丢帧问题
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
  static double a2d(AVRational a) {
  	return a.den  / a.num;
  }
  
  void FFRecoder::calc_pkt_ts(AVPacket* pkt, int frame_index) {
      //Duration between 2 frames (us)
      int64_t calc_duration=(double)AV_TIME_BASE/m_fps;
      //Parameters
      pkt->pts=(double)(frame_index*calc_duration)/(double)(av_q2d(codec_ctx_->time_base)*AV_TIME_BASE);
      pkt->dts=pkt->pts;
      pkt->duration=(double)calc_duration/(double)(av_q2d(codec_ctx_->time_base)*AV_TIME_BASE);
  }
  
  bool FFRecoder::write_pkt(AVPacket* new_pkt) {
  	frame_nb++;
  	calc_pkt_ts(new_pkt, frame_nb);
  	new_pkt->pts = av_rescale_q_rnd(new_pkt->pts, codec_ctx_->time_base, out_stream_->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
  	new_pkt->dts = av_rescale_q_rnd(new_pkt->dts, codec_ctx_->time_base, out_stream_->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
  	new_pkt->duration = av_rescale_q(new_pkt->duration, codec_ctx_->time_base, out_stream_->time_base);
  	new_pkt->stream_index = out_stream_->index;
  	// 将数据写入到输出流
  	int ret = av_interleaved_write_frame(fmt_ctx_, new_pkt);
  	
  	char errbuf[64]{ 0 };
  	if (ret < 0) {
  		fprintf(stderr, "Error while writing output packet: %s\n", av_make_error_string(errbuf, sizeof(errbuf), ret));
  		return false;
  	}
  
  	return true;
09c2d08c   Hu Chunming   arm交付版
242
243
  }
  
09a835c9   Hu Chunming   修正视频截存丢帧问题
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
  bool FFRecoder::write_pkt_data(const uint8_t* pkt_data, int pkt_size) {
  	AVPacket* new_pkt = av_packet_alloc();
  	av_new_packet(new_pkt, pkt_size);
  	memcpy(new_pkt->data, pkt_data, pkt_size);
  
  	frame_nb++;
  	calc_pkt_ts(new_pkt, frame_nb);
  	new_pkt->pts = av_rescale_q_rnd(new_pkt->pts, codec_ctx_->time_base, out_stream_->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
  	new_pkt->dts = av_rescale_q_rnd(new_pkt->dts, codec_ctx_->time_base, out_stream_->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
  	new_pkt->duration = av_rescale_q(new_pkt->duration, codec_ctx_->time_base, out_stream_->time_base);
  	new_pkt->stream_index = out_stream_->index;
  	// 将数据写入到输出流
  	int ret = av_interleaved_write_frame(fmt_ctx_, new_pkt);
  
  	av_packet_free(&new_pkt);
      new_pkt = nullptr;
  	
  	char errbuf[64]{ 0 };
  	if (ret < 0) {
  		fprintf(stderr, "Error while writing output packet: %s\n", av_make_error_string(errbuf, sizeof(errbuf), ret));
  		return false;
  	}
  
  	return true;
  }
  
  bool FFRecoder::flush()
e01a0397   Hu Chunming   代码优化;
271
  {
09a835c9   Hu Chunming   修正视频截存丢帧问题
272
  	return write_frame(nullptr);
e01a0397   Hu Chunming   代码优化;
273
274
  }
  
09c2d08c   Hu Chunming   arm交付版
275
276
277
278
279
  bool FFRecoder::bgr_to_yuv420p(const uint8_t* const buf_bgr, uint8_t* const buf_420p)
  {
  	// 分配转换上下文
  	thread_local std::tuple<int,int,int> params{ 0, 0, 0 };
  	thread_local std::unique_ptr<SwsContext, decltype(&sws_freeContext)> sws_context{ nullptr, &sws_freeContext };
09a835c9   Hu Chunming   修正视频截存丢帧问题
280
281
  	
  	std::tuple<int,int,int> new_params{ width_, height_, av_image_get_linesize(AV_PIX_FMT_YUV420P, width_, 0) };
09c2d08c   Hu Chunming   arm交付版
282
283
284
285
286
287
  	if (!sws_context || params != new_params)
  	{
  		sws_context.reset(sws_getContext(width_, height_, AV_PIX_FMT_BGR24, width_, height_,
  			AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr));
  		params = new_params;
  	}
09a835c9   Hu Chunming   修正视频截存丢帧问题
288
  
09c2d08c   Hu Chunming   arm交付版
289
290
  	// 转换格式
  	const int stride = std::get<2>(params);//Y平面一行的数据长度
09a835c9   Hu Chunming   修正视频截存丢帧问题
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
  	const int ret = sws_scale(sws_context.get(),
  		&buf_bgr,/* bgr数据只有一个平面 */
  		std::array<int, 1> {width_ * 3}.data(),/* BGR所以图像宽度*3 */
  		0, height_,
  		std::array<uint8_t* const, 3>{ buf_420p, buf_420p + y_size_, buf_420p + y_size_ + uv_size_ }.data(),/* YUV三个平面的起始地址 */
  		std::array<int, 3>{ stride, stride / 2, stride / 2 }.data()
          );/* YUV每个平面中一行的宽度 */
  
  	return ret >= 0;
  }
  
  bool FFRecoder::close()
  {
  	flush();
  	uninit();
09c2d08c   Hu Chunming   arm交付版
306
  }