Ticket #8036: FFMpegEncodingAPI.cpp

File FFMpegEncodingAPI.cpp, 9.1 KB (added by Patrick Weber, 7 years ago)

Sample source code.

Line 
1#include "Stdafx_unmanaged.h"
2#include "FFMpegEncodingAPI.h"
3
4#include "DebugUtilities.h"
5#include "FrameProcessingThread.h"
6#include "IAESemaphore.h"
7
8using namespace IAE::VideoProcessing;
9using namespace IEC::DriverUtils;
10
11//#define SHOW_DEBUG 1
12namespace IAE
13{
14 namespace VideoProcessing
15 {
16 namespace FFMPEG
17 {
18 const int FRAME_ERROR_ALLOWANCE = 10; // Maximum frames to wait for the frame semaphore to release.
19 const int MILLISECONDS_PER_SECOND = 1000;
20 /// <summary>
21 /// Unmanaged API interface
22 /// All interaction with the API should occur in the unmanaged space.
23 /// </summary>
24 /// <param name="iFrameHeight"></param>
25 /// <param name="iFrameWidth"></param>
26 /// <param name="iBytesPerPixel"></param>
27 /// <param name="iFPS"></param>
28 FFMpegEncodingAPI::FFMpegEncodingAPI (int iFrameHeight, int iFrameWidth, int iBytesPerPixel, int iFPS)
29 {
30 FrameGrabber::Initialize (iFrameHeight, iFrameWidth, iBytesPerPixel);
31
32 m_iFramesPerSecond = iFPS;
33 SetFrameRate (iFPS); // Assume the initial frame rate is really true.
34
35 PrintDebug (
36 "New FFMpegEncodingAPI created with frame dimensions: %dh x %dw bpp: %d (%d bytes) fps: %d\n",
37 m_iFrameHeight, m_iFrameWidth, m_iFrameBytesPerPixel, m_lFrameSize, m_iFramesPerSecond);
38
39 _thread = new FrameProcessingThread (this);
40 int iTimeout = (int) ((1 / ((double) iFPS)) * FRAME_ERROR_ALLOWANCE * MILLISECONDS_PER_SECOND);
41 _thread->SetWaitTimeout (iTimeout);
42 }
43
44 //==========================================================================
45 // Destruction.
46 //==========================================================================
47 FFMpegEncodingAPI::~FFMpegEncodingAPI ()
48 {
49 try
50 {
51 CleanUp (nullptr);
52 }
53 catch (...)
54 {
55 }
56 }
57
58 //==========================================================================
59 // Initialize the API and allocate buffers
60 //==========================================================================
61 bool FFMpegEncodingAPI::Initialize (AVCodecID codecID, AVPixelFormat pixFmt, int64_t lBitRate, const char* accel)
62 {
63 InitializeAPI ();
64 InitializeContext (codecID, pixFmt, lBitRate, accel);
65 AllocateBuffers ();
66 return _bIsInitialized;
67 }
68
69 /// <summary>
70 /// Set the presentation time scaling factor based on actual FPS.
71 /// </summary>
72 /// <param name="dFramesPerSecond"></param>
73 void FFMpegEncodingAPI::SetFrameRate (double dFramesPerSecond)
74 {
75 _dActualFPS = dFramesPerSecond;
76 _dTimeScaleFactor = ((double) m_iFramesPerSecond) / dFramesPerSecond;
77 }
78
79 //==========================================================================
80 // Perform FFMPEG API initializations
81 //==========================================================================
82 void FFMpegEncodingAPI::InitializeAPI ()
83 {
84#ifdef SHOW_DEBUG
85 av_log_set_callback (LoggingCallback);
86#endif
87#if FF_USE_DEPRECATED
88 av_register_all ();
89 avcodec_register_all ();
90 avfilter_register_all ();
91#endif
92 }
93
94 //==========================================================================
95 // Creates a context & AVFrame
96 // This is the heart of how things get encoded. The "Video Demystified" book
97 // is extremely helpful providing background on the various context settings.
98 //==========================================================================
99 void FFMpegEncodingAPI::InitializeContext (AVCodecID codecID, AVPixelFormat pixFmt, int64_t lBitRate, const char* accel)
100 {
101 // Set up the CODEC & context
102 _selectedCodec = codecID;
103 _incomingFormat = pixFmt;
104 m_encodingContext = nullptr;
105 _codec = nullptr;
106 auto temp = GetLogLevel ();
107 SetLogLevel (256);
108
109 _encodingFormat = (accel != nullptr && strcmp ("qsv", accel) != 0) ?
110 INTERNAL_FORMAT : AV_PIX_FMT_QSV;
111
112 auto name = GetEncoderName (accel);
113 if (name != nullptr)
114 _codec = avcodec_find_encoder_by_name (name);
115
116 if (_codec == nullptr)
117 {
118 _encodingFormat = INTERNAL_FORMAT;
119 _codec = avcodec_find_encoder (codecID);
120 }
121
122 if (_codec != nullptr)
123 {
124 PrintDebug ("Using CODEC %s.\n", _codec->name);
125 _avFrame = av_frame_alloc ();
126 if (_avFrame != nullptr)
127 {
128 m_encodingContext = avcodec_alloc_context3 (_codec);
129
130 _avFrame->format = _encodingFormat;
131 _avFrame->width = (int) ((m_iFrameWidth * m_dScaleFactor) + 0.5);
132 _avFrame->height = (int) ((m_iFrameHeight * m_dScaleFactor) + 0.5);
133
134 _swsContext = sws_getContext (m_iFrameWidth, m_iFrameHeight,
135 pixFmt, _avFrame->width, _avFrame->height, _encodingFormat,
136 SWS_BICUBIC, 0, 0, 0);
137 }
138 }
139 else
140 PrintDebug ("Codec not found!\n");
141
142 if (m_encodingContext != nullptr)
143 {
144 m_encodingContext->width = _avFrame->width;
145 m_encodingContext->height = _avFrame->height;
146 m_encodingContext->time_base = av_make_q (1, m_iFramesPerSecond);
147
148 SetAVOptions (lBitRate);
149 PrintDebug ("Frame Encoding Parameters: Width=%d, Height=%d, FPS=%d, BPS=%ld, GOP=%d\n",
150 _avFrame->width, _avFrame->height, m_iFramesPerSecond, lBitRate, m_iGroupOfPictureSize);
151
152 errCode = avcodec_open2 (m_encodingContext, _codec, NULL);
153 PrintError ("avcodec_open2");
154 _bCodecOpen = (errCode == 0);
155 if (!_bCodecOpen)
156 PrintDebug ("Error opening the Codec.\n");
157
158 _hd.bEncoderEnabled = _bCodecOpen;
159 _hd.iLastError = errCode;
160 }
161 else
162 PrintDebug ("Could not create a context for encoding.\n");
163 SetLogLevel (temp);
164 }
165
166 //==========================================================================
167 // Set the various options for FFMPEG encoding.
168 //==========================================================================
169 void FFMpegEncodingAPI::SetAVOptions (int64_t lBitRate)
170 {
171 m_encodingContext->bit_rate = lBitRate;
172
173 // Group of Pictures or Group of Visual Object Planes. See "Video Demystified",
174 // Chapter 13 for MPEG-2 and Chapter 14 for MPEG-4 and H264.
175 m_encodingContext->gop_size = m_iGroupOfPictureSize;
176 m_encodingContext->max_b_frames = 4;
177
178 m_encodingContext->pix_fmt = _encodingFormat;
179
180 av_opt_set_int (m_encodingContext, "zerolatency", 1, AV_OPT_SEARCH_CHILDREN);
181 SetCommonOptions (m_encodingContext->priv_data);
182
183 if (m_iFramesPerSecond < 15)
184 {
185 m_encodingContext->max_b_frames = 0;
186 av_opt_set_int (m_encodingContext, "bf", 0, AV_OPT_SEARCH_CHILDREN);
187 av_opt_set_int (m_encodingContext, "g", m_iGroupOfPictureSize, AV_OPT_SEARCH_CHILDREN);
188 }
189
190 if (_selectedCodec == AV_CODEC_ID_H264 || _selectedCodec == AV_CODEC_ID_H265)
191 SetH26xOptions (lBitRate);
192 }
193
194 void FFMpegEncodingAPI::SetCommonOptions (void* options)
195 {
196 av_opt_set_int (options, "slice-max-size", DEFAULT_MAX_SLICE_SIZE, 0);
197 av_opt_set_int (options, "ps", DEFAULT_RTP_PAYLOAD_SIZE, AV_OPT_SEARCH_CHILDREN);
198 av_opt_set_int (options, "threads", 4, AV_OPT_SEARCH_CHILDREN);
199 av_opt_set_int (options, "cabac", 0, AV_OPT_SEARCH_CHILDREN);
200 av_opt_set_int (options, "max_nal_size", DEFAULT_RTP_PAYLOAD_SIZE, AV_OPT_SEARCH_CHILDREN);
201 av_opt_set (options, "allow_skip_frames", "1", AV_OPT_SEARCH_CHILDREN);
202 av_opt_set (options, "slice_mode", "dyn", AV_OPT_SEARCH_CHILDREN);
203 av_opt_set (options, "loglevel", "fatal", 0);
204 }
205
206 /// <summary>
207 /// Set the H264 and H265 options.
208 /// </summary>
209 /// <param name="lBitRate"></param>
210 void FFMpegEncodingAPI::SetH26xOptions (int64_t lBitRate)
211 {
212 // See "Video Demystified" Chapter 14 to understand these modifiers, and ffmpeg -h full to
213 // see other available options.
214 if (_selectedCodec == AV_CODEC_ID_H264)
215 {
216 ...
217 else if (strcmp (_codec->name, "h264_qsv") == 0)
218 SetQSV264Options (m_encodingContext->priv_data);
219 }
220 }
221
222 void FFMpegEncodingAPI::SetQSV264Options (void* options)
223 {
224 av_opt_set (options, "preset", "veryfast", AV_OPT_SEARCH_CHILDREN);
225 av_opt_set (options, "profile", "baseline", AV_OPT_SEARCH_CHILDREN);
226 }
227
228 const char* FFMpegEncodingAPI::GetEncoderName (const char* accel)
229 {
230 const char* name = nullptr;
231 if (accel != nullptr)
232 {
233 switch (_selectedCodec)
234 {
235 case AV_CODEC_ID_H264:
236 strcpy_s (_codecName, "h264_");
237 strcat_s (_codecName, accel);
238 name = _codecName;
239 break;
240 case AV_CODEC_ID_H265:
241 strcpy_s (_codecName, "hevc_");
242 strcat_s (_codecName, accel);
243 name = _codecName;
244 break;
245 case AV_CODEC_ID_VP8:
246 strcpy_s (_codecName, "vp8_");
247 strcat_s (_codecName, accel);
248 name = _codecName;
249 break;
250 case AV_CODEC_ID_VP9:
251 strcpy_s (_codecName, "vp9_");
252 strcat_s (_codecName, accel);
253 name = _codecName;
254 break;
255 case AV_CODEC_ID_MPEG2VIDEO:
256 strcpy_s (_codecName, "mpeg2_");
257 strcat_s (_codecName, accel);
258 name = _codecName;
259 break;
260 default:
261 break;
262 }
263 if (name != nullptr)
264 PrintDebug ("Using accelerator %s\n", name);
265 }
266 return name;
267 }
268 }
269 }
270}