9354f60fc529cddb37c78974109e26bfd63dafda
[occt.git] / src / Media / Media_CodecContext.cxx
1 // Created by: Kirill GAVRILOV
2 // Copyright (c) 2019 OPEN CASCADE SAS
3 //
4 // This file is part of Open CASCADE Technology software library.
5 //
6 // This library is free software; you can redistribute it and/or modify it under
7 // the terms of the GNU Lesser General Public License version 2.1 as published
8 // by the Free Software Foundation, with special exception defined in the file
9 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
10 // distribution for complete text of the license and disclaimer of any warranty.
11 //
12 // Alternatively, this file may be used under the terms of Open CASCADE
13 // commercial license or contractual agreement.
14
15 // activate some C99 macros like UINT64_C in "stdint.h" which used by FFmpeg
16 #ifndef __STDC_CONSTANT_MACROS
17   #define __STDC_CONSTANT_MACROS
18 #endif
19
20 #include <Media_CodecContext.hxx>
21
22 #include <Media_Frame.hxx>
23 #include <Media_FormatContext.hxx>
24
25 #include <Message.hxx>
26 #include <Message_Messenger.hxx>
27 #include <OSD_Parallel.hxx>
28
29 #ifdef HAVE_FFMPEG
30 #include <Standard_WarningsDisable.hxx>
31 extern "C"
32 {
33   #include <libavformat/avformat.h>
34 };
35 #include <Standard_WarningsRestore.hxx>
36 #endif
37
38 IMPLEMENT_STANDARD_RTTIEXT(Media_CodecContext, Standard_Transient)
39
40 // =======================================================================
41 // function : Media_CodecContext
42 // purpose  :
43 // =======================================================================
44 Media_CodecContext::Media_CodecContext()
45 : myCodecCtx (NULL),
46   myCodec    (NULL),
47   myPtsStartBase  (0.0),
48   myPtsStartStream(0.0),
49   myTimeBase      (1.0),
50   myStreamIndex   (0),
51   myPixelAspectRatio (1.0f)
52 {
53 #ifdef HAVE_FFMPEG
54   myCodecCtx = avcodec_alloc_context3 (NULL);
55 #endif
56 }
57
58 // =======================================================================
59 // function : ~Media_CodecContext
60 // purpose  :
61 // =======================================================================
62 Media_CodecContext::~Media_CodecContext()
63 {
64   Close();
65 }
66
67 // =======================================================================
68 // function : Init
69 // purpose  :
70 // =======================================================================
71 bool Media_CodecContext::Init (const AVStream& theStream,
72                                double thePtsStartBase,
73                                int    theNbThreads)
74 {
75 #ifdef HAVE_FFMPEG
76   return Init (theStream, thePtsStartBase, theNbThreads, AV_CODEC_ID_NONE);
77 #else
78   return Init (theStream, thePtsStartBase, theNbThreads, 0);
79 #endif
80 }
81
82 // =======================================================================
83 // function : Init
84 // purpose  :
85 // =======================================================================
86 bool Media_CodecContext::Init (const AVStream& theStream,
87                                double thePtsStartBase,
88                                int theNbThreads,
89                                int theCodecId)
90 {
91 #ifdef HAVE_FFMPEG
92   myStreamIndex = theStream.index;
93   if (avcodec_parameters_to_context (myCodecCtx, theStream.codecpar) < 0)
94   {
95     Message::DefaultMessenger()->Send ("Internal error: unable to copy codec parameters", Message_Fail);
96     Close();
97     return false;
98   }
99
100   myTimeBase       = av_q2d (theStream.time_base);
101   myPtsStartBase   = thePtsStartBase;
102   myPtsStartStream = Media_FormatContext::StreamUnitsToSeconds (theStream, theStream.start_time);
103
104   const AVCodecID aCodecId = theCodecId != AV_CODEC_ID_NONE ? (AVCodecID )theCodecId : theStream.codecpar->codec_id;
105   myCodec = avcodec_find_decoder (aCodecId);
106   if (myCodec == NULL)
107   {
108     Message::DefaultMessenger()->Send ("FFmpeg: unable to find decoder", Message_Fail);
109     Close();
110     return false;
111   }
112
113   myCodecCtx->codec_id = aCodecId;
114   AVDictionary* anOpts = NULL;
115   av_dict_set (&anOpts, "refcounted_frames", "1", 0);
116   if (theStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
117   {
118     myCodecCtx->thread_count = theNbThreads <= -1 ? OSD_Parallel::NbLogicalProcessors() : theNbThreads;
119   }
120
121   if (avcodec_open2 (myCodecCtx, myCodec, &anOpts) < 0)
122   {
123     Message::DefaultMessenger()->Send ("FFmpeg: unable to open decoder", Message_Fail);
124     Close();
125     return false;
126   }
127
128   myPixelAspectRatio = 1.0f;
129   if (theStream.sample_aspect_ratio.num && av_cmp_q(theStream.sample_aspect_ratio, myCodecCtx->sample_aspect_ratio))
130   {
131     myPixelAspectRatio = float(theStream.sample_aspect_ratio.num) / float(theStream.sample_aspect_ratio.den);
132   }
133   else
134   {
135     if (myCodecCtx->sample_aspect_ratio.num == 0
136      || myCodecCtx->sample_aspect_ratio.den == 0)
137     {
138       myPixelAspectRatio = 1.0f;
139     }
140     else
141     {
142       myPixelAspectRatio = float(myCodecCtx->sample_aspect_ratio.num) / float(myCodecCtx->sample_aspect_ratio.den);
143     }
144   }
145
146   if (theStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO
147    && (myCodecCtx->width  <= 0
148     || myCodecCtx->height <= 0))
149   {
150     Message::DefaultMessenger()->Send ("FFmpeg: video stream has invalid dimensions", Message_Fail);
151     Close();
152     return false;
153   }
154
155   return true;
156 #else
157   (void )&theStream;
158   (void )thePtsStartBase;
159   (void )theNbThreads;
160   (void )theCodecId;
161   return false;
162 #endif
163 }
164
165 // =======================================================================
166 // function : Close
167 // purpose  :
168 // =======================================================================
169 void Media_CodecContext::Close()
170 {
171   if (myCodecCtx != NULL)
172   {
173   #ifdef HAVE_FFMPEG
174     avcodec_free_context (&myCodecCtx);
175   #endif
176   }
177 }
178
179 // =======================================================================
180 // function : Flush
181 // purpose  :
182 // =======================================================================
183 void Media_CodecContext::Flush()
184 {
185   if (myCodecCtx != NULL)
186   {
187   #ifdef HAVE_FFMPEG
188     avcodec_flush_buffers (myCodecCtx);
189   #endif
190   }
191 }
192
193 // =======================================================================
194 // function : SizeX
195 // purpose  :
196 // =======================================================================
197 int Media_CodecContext::SizeX() const
198 {
199 #ifdef HAVE_FFMPEG
200   return (myCodecCtx != NULL) ? myCodecCtx->width : 0;
201 #else
202   return 0;
203 #endif
204 }
205
206 // =======================================================================
207 // function : SizeY
208 // purpose  :
209 // =======================================================================
210 int Media_CodecContext::SizeY() const
211 {
212 #ifdef HAVE_FFMPEG
213   return (myCodecCtx != NULL) ? myCodecCtx->height : 0;
214 #else
215   return 0;
216 #endif
217 }
218
219 // =======================================================================
220 // function : CanProcessPacket
221 // purpose  :
222 // =======================================================================
223 bool Media_CodecContext::CanProcessPacket (const Handle(Media_Packet)& thePacket) const
224 {
225   return !thePacket.IsNull()
226        && myStreamIndex == thePacket->StreamIndex();
227 }
228
229 // =======================================================================
230 // function : SendPacket
231 // purpose  :
232 // =======================================================================
233 bool Media_CodecContext::SendPacket (const Handle(Media_Packet)& thePacket)
234 {
235   if (!CanProcessPacket (thePacket))
236   {
237     return false;
238   }
239
240 #ifdef HAVE_FFMPEG
241   const int aRes = avcodec_send_packet (myCodecCtx, thePacket->Packet());
242   if (aRes < 0 && aRes != AVERROR_EOF)
243   {
244     return false;
245   }
246   return true;
247 #else
248   return false;
249 #endif
250 }
251
252 // =======================================================================
253 // function : ReceiveFrame
254 // purpose  :
255 // =======================================================================
256 bool Media_CodecContext::ReceiveFrame (const Handle(Media_Frame)& theFrame)
257 {
258   if (theFrame.IsNull())
259   {
260     return false;
261   }
262
263 #ifdef HAVE_FFMPEG
264   const int aRes2 = avcodec_receive_frame (myCodecCtx, theFrame->ChangeFrame());
265   if (aRes2 < 0)
266   {
267     return false;
268   }
269
270   const int64_t aPacketPts = theFrame->BestEffortTimestamp() != AV_NOPTS_VALUE ? theFrame->BestEffortTimestamp() : 0;
271   const double aFramePts  = double(aPacketPts) * myTimeBase - myPtsStartBase;
272   theFrame->SetPts (aFramePts);
273   return true;
274 #else
275   return false;
276 #endif
277 }