Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2001-2024 German Aerospace Center (DLR) and others.
4 : // This program and the accompanying materials are made available under the
5 : // terms of the Eclipse Public License 2.0 which is available at
6 : // https://www.eclipse.org/legal/epl-2.0/
7 : // This Source Code may also be made available under the following Secondary
8 : // Licenses when the conditions for such availability set forth in the Eclipse
9 : // Public License 2.0 are satisfied: GNU General Public License, version 2
10 : // or later which is available at
11 : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13 : /****************************************************************************/
14 : /// @file GUIVideoEncoder.h
15 : /// @author Michael Behrisch
16 : /// @date Dec 2015
17 : ///
18 : // A simple video encoder from RGBA pics to anything ffmpeg can handle.
19 : // Tested with h264 only.
20 : // Inspired by Lei Xiaohua, Philip Schneider and Fabrice Bellard, see
21 : // https://github.com/leixiaohua1020/simplest_ffmpeg_video_encoder and
22 : // https://github.com/codefromabove/FFmpegRGBAToYUV
23 : /****************************************************************************/
24 : #pragma once
25 : #include <config.h>
26 :
27 : #include <stdio.h>
28 : #include <iostream>
29 : #include <stdexcept>
30 :
31 : #define __STDC_CONSTANT_MACROS
32 :
33 : #ifdef _MSC_VER
34 : #pragma warning(push)
35 : #pragma warning(disable: 4242 4244) // do not warn about integer conversions
36 : #endif
37 : #if __GNUC__ > 3
38 : #pragma GCC diagnostic push
39 : #pragma GCC diagnostic ignored "-Wpedantic"
40 : #pragma GCC diagnostic ignored "-Wvariadic-macros"
41 : #endif
42 : extern "C"
43 : {
44 : #include <libavutil/opt.h>
45 : #include <libavutil/imgutils.h>
46 : #include <libavcodec/avcodec.h>
47 : #include <libavformat/avformat.h>
48 : #include <libswscale/swscale.h>
49 : }
50 : #ifdef _MSC_VER
51 : #pragma warning(pop)
52 : #endif
53 : #if __GNUC__ > 3
54 : #pragma GCC diagnostic pop
55 : #endif
56 :
57 : #include <utils/common/MsgHandler.h>
58 : #include <utils/common/ToString.h>
59 :
60 :
61 : // ===========================================================================
62 : // class definitions
63 : // ===========================================================================
64 : /**
65 : * @class GUIVideoEncoder
66 : * @brief A simple video encoder from RGBA pics to anything ffmpeg can handle.
67 : */
68 : class GUIVideoEncoder {
69 : public:
70 0 : GUIVideoEncoder(const char* const out_file, const int width, const int height, double frameDelay) {
71 : //av_register_all();
72 0 : avformat_alloc_output_context2(&myFormatContext, NULL, NULL, out_file);
73 0 : if (myFormatContext == nullptr) {
74 0 : throw ProcessError(TL("Unknown format!"));
75 : }
76 :
77 : // @todo maybe warn about default and invalid framerates
78 : int framerate = 25;
79 0 : if (frameDelay > 0.) {
80 0 : framerate = (int)(1000. / frameDelay);
81 0 : if (framerate <= 0) {
82 : framerate = 1;
83 : }
84 : }
85 0 : AVStream* const video_st = avformat_new_stream(myFormatContext, 0);
86 0 : video_st->time_base.num = 1;
87 0 : video_st->time_base.den = framerate;
88 :
89 0 : const AVCodec* codec = avcodec_find_encoder(myFormatContext->oformat->video_codec);
90 0 : if (codec == nullptr) {
91 0 : WRITE_WARNING(TL("Unknown codec, falling back to HEVC!"));
92 0 : codec = avcodec_find_encoder_by_name("libx265");
93 : }
94 0 : if (codec == nullptr) {
95 0 : throw ProcessError(TL("Unknown codec!"));
96 : }
97 : //Param that must set
98 0 : myCodecCtx = avcodec_alloc_context3(codec);
99 0 : if (myCodecCtx == nullptr) {
100 0 : throw ProcessError(TL("Could not allocate video codec context!"));
101 : }
102 : //pmyCodecCtx->codec_id =AV_CODEC_ID_HEVC;
103 : //pmyCodecCtx->codec_id = pFormatCtx->oformat->video_codec;
104 : //pmyCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
105 0 : myCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
106 : // @todo maybe warn about one missing line for odd width or height
107 0 : myCodecCtx->width = (width / 2) * 2;
108 0 : myCodecCtx->height = (height / 2) * 2;
109 0 : myCodecCtx->time_base.num = 1;
110 0 : myCodecCtx->time_base.den = framerate;
111 0 : myCodecCtx->framerate.num = framerate;
112 0 : myCodecCtx->framerate.den = 1;
113 0 : myCodecCtx->bit_rate = 4000000; // example has 400000
114 0 : myCodecCtx->gop_size = 10; // example has 10
115 : //H264
116 : //pmyCodecCtx->me_range = 16;
117 : //pmyCodecCtx->max_qdiff = 4;
118 : //pmyCodecCtx->qcompress = 0.6;
119 : //myCodecCtx->qmin = 10; // example does not set this
120 : //myCodecCtx->qmax = 51; // example does not set this
121 : //myCodecCtx->max_b_frames = 1; // example has 1
122 :
123 : // Set codec specific options
124 : //H.264
125 0 : if (myCodecCtx->codec_id == AV_CODEC_ID_H264) {
126 0 : av_opt_set(myCodecCtx->priv_data, "preset", "slow", 0);
127 : //av_opt_set(myCodecCtx->priv_data, "tune", "zerolatency", 0);
128 : //av_opt_set(myCodecCtx->priv_data, "profile", "main", 0);
129 : }
130 : //H.265
131 0 : if (myCodecCtx->codec_id == AV_CODEC_ID_HEVC) {
132 0 : av_opt_set(myCodecCtx->priv_data, "preset", "ultrafast", 0);
133 0 : av_opt_set(myCodecCtx->priv_data, "tune", "zero-latency", 0);
134 : }
135 0 : if (avcodec_open2(myCodecCtx, codec, nullptr) < 0) {
136 0 : throw ProcessError(TL("Could not open codec!"));
137 : }
138 0 : avcodec_parameters_from_context(video_st->codecpar, myCodecCtx);
139 :
140 0 : myFrame = av_frame_alloc();
141 0 : if (myFrame == nullptr) {
142 0 : throw ProcessError(TL("Could not allocate video frame!"));
143 : }
144 0 : myFrame->format = myCodecCtx->pix_fmt;
145 0 : myFrame->width = myCodecCtx->width;
146 0 : myFrame->height = myCodecCtx->height;
147 0 : if (av_frame_get_buffer(myFrame, 32) < 0) {
148 0 : throw ProcessError(TL("Could not allocate the video frame data!"));
149 : }
150 0 : mySwsContext = sws_getContext(myCodecCtx->width, myCodecCtx->height, AV_PIX_FMT_RGBA,
151 0 : myCodecCtx->width, myCodecCtx->height, AV_PIX_FMT_YUV420P,
152 : 0, 0, 0, 0);
153 : //Open output URL
154 0 : if (avio_open(&myFormatContext->pb, out_file, AVIO_FLAG_WRITE) < 0) {
155 0 : throw ProcessError(TL("Failed to open output file!"));
156 : }
157 :
158 : //Write File Header
159 0 : if (avformat_write_header(myFormatContext, nullptr) < 0) {
160 0 : throw ProcessError(TL("Failed to write file header!"));
161 : }
162 0 : myFrameIndex = 0;
163 0 : myPkt = av_packet_alloc();
164 0 : if (myPkt == nullptr) {
165 0 : throw ProcessError(TL("Could not allocate video packet!"));
166 : }
167 0 : }
168 :
169 0 : ~GUIVideoEncoder() {
170 : int ret = 1;
171 0 : if (!(myCodecCtx->codec->capabilities & AV_CODEC_CAP_DELAY)) {
172 : ret = 0;
173 : }
174 0 : if (avcodec_send_frame(myCodecCtx, nullptr) < 0) {
175 0 : WRITE_WARNING(TL("Error sending final frame!"));
176 : ret = -1;
177 : }
178 0 : while (ret >= 0) {
179 0 : ret = avcodec_receive_packet(myCodecCtx, myPkt);
180 0 : if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
181 : break;
182 0 : } else if (ret < 0) {
183 0 : WRITE_WARNING(TL("Error during final encoding step!"));
184 0 : break;
185 : }
186 0 : ret = av_write_frame(myFormatContext, myPkt);
187 0 : av_packet_unref(myPkt);
188 : }
189 :
190 : //Write file trailer
191 0 : av_write_trailer(myFormatContext);
192 0 : avio_closep(&myFormatContext->pb);
193 :
194 : //Clean
195 0 : avcodec_free_context(&myCodecCtx);
196 0 : av_frame_free(&myFrame);
197 0 : av_packet_free(&myPkt);
198 0 : avformat_free_context(myFormatContext);
199 0 : }
200 :
201 0 : void writeFrame(uint8_t* buffer) {
202 0 : if (av_frame_make_writable(myFrame) < 0) {
203 0 : throw ProcessError();
204 : }
205 0 : uint8_t* inData[1] = { buffer }; // RGBA32 has one plane
206 0 : int inLinesize[1] = { 4 * myCodecCtx->width }; // RGBA stride
207 0 : sws_scale(mySwsContext, inData, inLinesize, 0, myCodecCtx->height,
208 0 : myFrame->data, myFrame->linesize);
209 0 : myFrame->pts = myFrameIndex;
210 0 : int r = avcodec_send_frame(myCodecCtx, myFrame);
211 0 : if (r < 0) {
212 : char errbuf[64];
213 0 : av_strerror(r, errbuf, 64);
214 0 : throw ProcessError(TL("Error sending frame for encoding!"));
215 : }
216 : int ret = 0;
217 0 : while (ret >= 0) {
218 0 : ret = avcodec_receive_packet(myCodecCtx, myPkt);
219 0 : if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
220 : break;
221 0 : } else if (ret < 0) {
222 0 : throw ProcessError(TL("Error during encoding!"));
223 : }
224 : /* rescale output packet timestamp values from codec to stream timebase */
225 0 : av_packet_rescale_ts(myPkt, myCodecCtx->time_base, myFormatContext->streams[0]->time_base);
226 0 : myPkt->stream_index = 0;
227 0 : ret = av_write_frame(myFormatContext, myPkt);
228 0 : av_packet_unref(myPkt);
229 : }
230 0 : myFrameIndex++;
231 0 : }
232 :
233 : private:
234 : AVFormatContext* myFormatContext;
235 : SwsContext* mySwsContext;
236 : AVCodecContext* myCodecCtx;
237 : AVFrame* myFrame;
238 : AVPacket* myPkt;
239 : int myFrameIndex;
240 :
241 : };
|