| 1 | #include "Stdafx_unmanaged.h"
|
|---|
| 2 | #include "FFMpegEncodingAPI.h"
|
|---|
| 3 |
|
|---|
| 4 | #include "DebugUtilities.h"
|
|---|
| 5 | #include "FrameProcessingThread.h"
|
|---|
| 6 | #include "IAESemaphore.h"
|
|---|
| 7 |
|
|---|
| 8 | using namespace IAE::VideoProcessing;
|
|---|
| 9 | using namespace IEC::DriverUtils;
|
|---|
| 10 |
|
|---|
| 11 | //#define SHOW_DEBUG 1
|
|---|
| 12 | namespace 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 | }
|
|---|