xcal
基于 C++23 的现代图形渲染引擎
载入中...
搜索中...
未找到
ffmpegcodec.cc
浏览该文件的文档.
1#include "ffmpegcodec.hpp"
2
3#include <xcal/public.h>
4
5extern "C" {
6#include <libavcodec/avcodec.h>
7#include <libavformat/avformat.h>
8#include <libavutil/imgutils.h>
9#include <libavutil/opt.h>
10#include <libswscale/swscale.h>
11}
12#include <iostream>
14
16 int width, int height,
17 int frame_rate)
18 : AbsVideoCodec(filename, width, height, frame_rate),
19 encoded_frames_(0),
20 format_ctx_(nullptr),
21 codec_ctx_(nullptr),
22 video_stream_(nullptr),
23 frame_(nullptr),
24 rgba_frame_(nullptr),
25 sws_ctx_(nullptr),
26 initialized_(false),
27 finished_(false) {}
28
30 if (!finished_) {
31 finish();
32 }
33
34 // 清理资源
35 if (sws_ctx_) {
36 sws_freeContext(sws_ctx_);
37 sws_ctx_ = nullptr;
38 }
39
40 if (frame_) {
41 av_frame_free(&frame_);
42 }
43
44 if (rgba_frame_) {
45 av_frame_free(&rgba_frame_);
46 }
47
48 if (codec_ctx_) {
49 avcodec_free_context(&codec_ctx_);
50 }
51
52 if (format_ctx_) {
53 if (format_ctx_->pb) {
54 avio_closep(&format_ctx_->pb);
55 }
56 avformat_free_context(format_ctx_);
57 }
58}
59
61 if (initialized_) {
62 return true;
63 }
64
65 avformat_network_init();
66
67 // 创建输出格式上下文
68 int ret = avformat_alloc_output_context2(&format_ctx_, nullptr, nullptr,
69 filename().c_str());
70 if (ret < 0) {
71 std::cerr << "Could not create output context: " << ret << std::endl;
72 return false;
73 }
74
75 // 查找编码器
76 const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
77 if (!codec) {
78 std::cerr << "H.264 encoder not found" << std::endl;
79 return false;
80 }
81
82 // 创建视频流
83 video_stream_ = avformat_new_stream(format_ctx_, codec);
84 if (!video_stream_) {
85 std::cerr << "Could not create video stream" << std::endl;
86 return false;
87 }
88
89 // 配置编码器上下文
90 codec_ctx_ = avcodec_alloc_context3(codec);
91 if (!codec_ctx_) {
92 std::cerr << "Could not allocate codec context" << std::endl;
93 return false;
94 }
95
96 codec_ctx_->codec_id = AV_CODEC_ID_H264;
97 codec_ctx_->bit_rate = 4000000; // 4 Mbps
98 codec_ctx_->width = width();
99 codec_ctx_->height = height();
100 codec_ctx_->time_base = {1, frame_rate()};
101 codec_ctx_->framerate = {frame_rate(), 1};
102 codec_ctx_->gop_size = 12;
103 codec_ctx_->max_b_frames = 2;
104 codec_ctx_->pix_fmt = AV_PIX_FMT_YUV420P;
105
106 // 设置编码器预设
107 av_opt_set(codec_ctx_->priv_data, "preset", "medium", 0);
108 av_opt_set(codec_ctx_->priv_data, "crf", "23", 0);
109
110 // 打开编码器
111 ret = avcodec_open2(codec_ctx_, codec, nullptr);
112 if (ret < 0) {
113 std::cerr << "Could not open codec: " << ret << std::endl;
114 return false;
115 }
116
117 // 将编码器参数复制到流中
118 ret = avcodec_parameters_from_context(video_stream_->codecpar, codec_ctx_);
119 if (ret < 0) {
120 std::cerr << "Could not copy codec parameters: " << ret << std::endl;
121 return false;
122 }
123
124 video_stream_->time_base = codec_ctx_->time_base;
125
126 // 打开输出文件
127 if (!(format_ctx_->oformat->flags & AVFMT_NOFILE)) {
128 ret = avio_open(&format_ctx_->pb, filename().c_str(), AVIO_FLAG_WRITE);
129 if (ret < 0) {
130 std::cerr << "Could not open output file: " << ret << std::endl;
131 return false;
132 }
133 }
134
135 // 写入文件头
136 ret = avformat_write_header(format_ctx_, nullptr);
137 if (ret < 0) {
138 std::cerr << "Error writing header: " << ret << std::endl;
139 return false;
140 }
141
142 // 创建YUV帧
143 frame_ = av_frame_alloc();
144 if (!frame_) {
145 std::cerr << "Could not allocate video frame" << std::endl;
146 return false;
147 }
148
149 frame_->format = codec_ctx_->pix_fmt;
150 frame_->width = codec_ctx_->width;
151 frame_->height = codec_ctx_->height;
152
153 ret = av_frame_get_buffer(frame_, 0);
154 if (ret < 0) {
155 std::cerr << "Could not allocate frame data: " << ret << std::endl;
156 return false;
157 }
158
159 // 创建RGBA帧
160 rgba_frame_ = av_frame_alloc();
161 if (!rgba_frame_) {
162 std::cerr << "Could not allocate RGBA frame" << std::endl;
163 return false;
164 }
165
166 rgba_frame_->format = AV_PIX_FMT_RGBA;
167 rgba_frame_->width = width();
168 rgba_frame_->height = height();
169
170 ret = av_frame_get_buffer(rgba_frame_, 0);
171 if (ret < 0) {
172 std::cerr << "Could not allocate RGBA frame data: " << ret << std::endl;
173 return false;
174 }
175
176 // 设置图像缩放转换器
177 if (!setup_scaler()) {
178 std::cerr << "Could not setup scaler" << std::endl;
179 return false;
180 }
181
182 initialized_ = true;
183 std::cout << "Video encoder initialized successfully" << std::endl;
184 return true;
185}
186
187bool xcal::render::codec::FfmpegCodec::setup_scaler() {
188 sws_ctx_ = sws_getContext(width(), height(), AV_PIX_FMT_RGBA, width(),
189 height(), AV_PIX_FMT_YUV420P, SWS_BILINEAR,
190 nullptr, nullptr, nullptr);
191
192 return sws_ctx_ != nullptr;
193}
194
196 const std::vector<char>& rgba_data) {
197 if (!initialized_ && !open()) {
198 return false;
199 }
200
201 if (finished_) {
202 std::cerr << "Encoder already finished" << std::endl;
203 return false;
204 }
205
206 if (rgba_data.size() != static_cast<size_t>(width() * height() * 4)) {
207 std::cerr << "Invalid RGBA data size. Expected: "
208 << width() * height() * 4 << ", Got: " << rgba_data.size()
209 << std::endl;
210 return false;
211 }
212
213 // 复制RGBA数据到帧中
214 for (int y = 0; y < height(); y++) {
215 memcpy(rgba_frame_->data[0] + y * rgba_frame_->linesize[0],
216 rgba_data.data() + y * width() * 4, width() * 4);
217 }
218
219 rgba_frame_->pts = encoded_frames_;
220
221 // 转换RGBA到YUV
222 sws_scale(sws_ctx_, rgba_frame_->data, rgba_frame_->linesize, 0, height(),
223 frame_->data, frame_->linesize);
224
225 frame_->pts = encoded_frames_;
226
227 // 编码帧
228 if (!encode_frame(frame_)) {
229 return false;
230 }
231
232 encoded_frames_++;
233 return true;
234}
235
236xcal::bool_t xcal::render::codec::FfmpegCodec::encode_frame(AVFrame* frame) {
237 int ret = avcodec_send_frame(codec_ctx_, frame);
238 if (ret < 0) {
239 std::cerr << "Error sending frame to encoder: " << ret << std::endl;
240 return false;
241 }
242
243 // 使用 av_packet_alloc 而不是已弃用的 av_init_packet
244 AVPacket* packet = av_packet_alloc();
245 if (!packet) {
246 std::cerr << "Could not allocate packet" << std::endl;
247 return false;
248 }
249
250 while (ret >= 0) {
251 ret = avcodec_receive_packet(codec_ctx_, packet);
252 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
253 break;
254 } else if (ret < 0) {
255 std::cerr << "Error encoding frame: " << ret << std::endl;
256 av_packet_free(&packet);
257 return false;
258 }
259
260 // 调整时间戳
261 av_packet_rescale_ts(packet, codec_ctx_->time_base,
262 video_stream_->time_base);
263 packet->stream_index = video_stream_->index;
264
265 // 写入包
266 ret = av_interleaved_write_frame(format_ctx_, packet);
267 if (ret < 0) {
268 std::cerr << "Error writing packet: " << ret << std::endl;
269 av_packet_free(&packet);
270 return false;
271 }
272
273 av_packet_unref(packet);
274 }
275
276 av_packet_free(&packet);
277 return true;
278}
279
281 if (finished_) {
282 return true;
283 }
284
285 if (!initialized_) {
286 std::cerr << "Encoder not initialized" << std::endl;
287 return false;
288 }
289
290 // 刷新编码器
291 encode_frame(nullptr);
292
293 // 写入文件尾
294 int ret = av_write_trailer(format_ctx_);
295 if (ret < 0) {
296 std::cerr << "Error writing trailer: " << ret << std::endl;
297 return false;
298 }
299
300 // 关闭输出文件
301 if (format_ctx_ && !(format_ctx_->oformat->flags & AVFMT_NOFILE)) {
302 avio_closep(&format_ctx_->pb);
303 }
304
305 finished_ = true;
306 std::cout << "Encoding completed. Total frames: " << encoded_frames_
307 << std::endl;
308 return true;
309}
FfmpegCodec(const std::string &filename, int width, int height, int frame_rate)
bool_t append_frame(const std::vector< char > &rgba_data) override
bool bool_t
Definition public.h:28