MythTV master
mythmediacodeccontext.cpp
Go to the documentation of this file.
1// Qt
2#include <QtGlobal>
3#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
4#include <QtAndroidExtras>
5#include <QAndroidJniEnvironment>
6#include <algorithm>
7#else
8#include <QCoreApplication>
9#include <QJniEnvironment>
10#include <QJniObject>
11#define QAndroidJniEnvironment QJniEnvironment
12#define QAndroidJniObject QJniObject
13#endif
14
15// MythTV
19
20#include "avformatdecoder.h"
22#include "mythplayerui.h"
24
25// FFmpeg
26extern "C" {
27#include "libavutil/pixfmt.h"
28#include "libavutil/hwcontext_mediacodec.h"
29#include "libavcodec/mediacodec.h"
30#include "libavcodec/avcodec.h"
31}
32
33#define LOC QString("MediaCodec: ")
34
35// MedicaCodec profile constants from MediaCodecInfo.CodecProfileLevel
36// NOLINTBEGIN(cppcoreguidelines-use-enum-class)
37enum MC_MPEG2 : uint8_t {
44};
48 MC_MPEG4_CORE = 0x0004,
49 MC_MPEG4_MAIN = 0x0008,
50 MC_MPEG4_NBIT = 0x0010,
62};
63enum MC_H264 : uint32_t {
65 MC_H264_MAIN = 0x00002,
67 MC_H264_HIGH = 0x00008,
68 MC_H264_HIGH10 = 0x00010,
69 MC_H264_HIGH422 = 0x00020,
70 MC_H264_HIGH444 = 0x00040,
73};
75 MC_HEVC_MAIN = 0x0001,
80};
81enum MC_VP8 : uint8_t {
82 MC_VP8_MAIN = 0x0001,
83};
85 MC_VP9_0 = 0x0001,
86 MC_VP9_1 = 0x0002,
87 MC_VP9_2 = 0x0004,
88 MC_VP9_3 = 0x0008,
89 MC_VP9_2HDR = 0x1000,
90 MC_VP9_3HDR = 0x2000,
93};
94// NOLINTEND(cppcoreguidelines-use-enum-class)
95
97{
98 if (Codec == MythCodecContext::MPEG2)
99 {
100 switch (Profile)
101 {
108 default: return MythCodecContext::MPEG2;
109 }
110 }
111
112 if (Codec == MythCodecContext::MPEG4)
113 {
114 switch (Profile)
115 {
123 case MC_MPEG4_SIMPLE_FBA: return MythCodecContext::MPEG4SimpleStudio; // Is this correct?
132 default: return MythCodecContext::MPEG4;
133 }
134 }
135
136 if (Codec == MythCodecContext::H264)
137 {
138 switch (Profile)
139 {
149 default: return MythCodecContext::H264;
150 }
151 }
152
153 if (Codec == MythCodecContext::HEVC)
154 {
155 switch (Profile)
156 {
162 default: return MythCodecContext::HEVC;
163 }
164 }
165
166 if (Codec == MythCodecContext::VP8)
168
169 if (Codec == MythCodecContext::VP9)
170 {
171 switch (Profile)
172 {
181 default: return MythCodecContext::VP9;
182 }
183 }
184
186}
187
189{
190 if (!Context || !gCoreContext->IsUIThread())
191 return -1;
192
193 // The interop must have a reference to the ui player so it can be deleted
194 // from the main thread.
195 auto * player = GetPlayerUI(Context);
196 if (!player)
197 return -1;
198
199 // Retrieve OpenGL render context
200 auto * render = dynamic_cast<MythRenderOpenGL*>(player->GetRender());
201 if (!render)
202 return -1;
203 OpenGLLocker locker(render);
204
205 // Create interop - NB no interop check here or in MythMediaCodecInterop
206 QSize size(Context->width, Context->height);
207 auto * interop = MythMediaCodecInterop::CreateMediaCodec(player, render, size);
208 if (!interop)
209 return -1;
210 if (!interop->GetSurface())
211 {
212 interop->DecrRef();
213 return -1;
214 }
215
216 // Create the hardware context
217 AVBufferRef *hwdeviceref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_MEDIACODEC);
218 auto *hwdevicectx = reinterpret_cast<AVHWDeviceContext*>(hwdeviceref->data);
219 hwdevicectx->free = &MythCodecContext::DeviceContextFinished;
220 hwdevicectx->user_opaque = interop;
221 auto *hwctx = reinterpret_cast<AVMediaCodecDeviceContext*>(hwdevicectx->hwctx);
222 hwctx->surface = interop->GetSurface();
223 if (av_hwdevice_ctx_init(hwdeviceref) < 0)
224 {
225 LOG(VB_GENERAL, LOG_ERR, LOC + "av_hwdevice_ctx_init failed");
226 av_buffer_unref(&hwdeviceref);
227 return -1;
228 }
229
230 Context->hw_device_ctx = hwdeviceref;
231 LOG(VB_GENERAL, LOG_INFO, LOC + "Created MediaCodec hardware device context");
232 return 0;
233}
234
236 const AVCodec **Codec,
237 const QString &Decoder,
238 AVStream *Stream,
239 uint StreamType)
240{
241 bool decodeonly = Decoder == "mediacodec-dec";
242 auto success = static_cast<MythCodecID>((decodeonly ? kCodec_MPEG1_MEDIACODEC_DEC : kCodec_MPEG1_MEDIACODEC) + (StreamType - 1));
243 auto failure = static_cast<MythCodecID>(kCodec_MPEG1 + (StreamType - 1));
244
245 if (!Decoder.startsWith("mediacodec"))
246 return failure;
247
248 if (!HaveMediaCodec())
249 return failure;
250
251 if (!decodeonly)
252 if (!FrameTypeIsSupported(*Context, FMT_MEDIACODEC))
253 return failure;
254
255 bool found = false;
258 MythCodecContext::FFmpegToMythProfile((*Context)->codec_id, (*Context)->profile);
259 for (auto profile : std::as_const(profiles))
260 {
261 if (profile.first == mythprofile &&
262 profile.second.width() >= (*Context)->width &&
263 profile.second.height() >= (*Context)->height)
264 {
265 found = true;
266 break;
267 }
268 }
269
270 auto *decoder = dynamic_cast<AvFormatDecoder*>(reinterpret_cast<DecoderBase*>((*Context)->opaque));
271 QString profilestr = MythCodecContext::GetProfileDescription(mythprofile, QSize());
272 if (found && decoder)
273 {
274 QString decodername = QString((*Codec)->name) + "_mediacodec";
275 if (decodername == "mpeg2video_mediacodec")
276 decodername = "mpeg2_mediacodec";
277 const AVCodec *newCodec = avcodec_find_decoder_by_name (decodername.toLocal8Bit().constData());
278 if (newCodec)
279 {
280 *Codec = newCodec;
281 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("HW device type '%1' supports decoding '%2' (%3)")
282 .arg(av_hwdevice_get_type_name(AV_HWDEVICE_TYPE_MEDIACODEC)).arg((*Codec)->name).arg(profilestr));
283 decoder->CodecMap()->FreeCodecContext(Stream);
284 *Context = decoder->CodecMap()->GetCodecContext(Stream, *Codec);
285 (*Context)->pix_fmt = AV_PIX_FMT_MEDIACODEC;
286 return success;
287 }
288 }
289
290 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("HW device type '%1' does not support decoding '%2' (%3)")
291 .arg(av_hwdevice_get_type_name(AV_HWDEVICE_TYPE_MEDIACODEC)).arg((*Codec)->name).arg(profilestr));
292 return failure;
293}
294
296 : MythCodecContext(Parent, CodecID)
297{
298}
299
300void MythMediaCodecContext::InitVideoCodec(AVCodecContext *Context, bool SelectedStream, bool &DirectRendering)
301{
302 if (CODEC_IS_MEDIACODEC(Context->codec))
303 {
304 Context->get_format = MythMediaCodecContext::GetFormat;
305 Context->slice_flags = SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD;
306 DirectRendering = false;
307 return;
308 }
309
310 MythCodecContext::InitVideoCodec(Context, SelectedStream, DirectRendering);
311}
312
313int MythMediaCodecContext::HwDecoderInit(AVCodecContext *Context)
314{
316 return 0;
318 return MythCodecContext::InitialiseDecoder2(Context, MythMediaCodecContext::InitialiseDecoder, "Create MediaCodec decoder");
319 return -1;
320}
321
322bool MythMediaCodecContext::RetrieveFrame(AVCodecContext *Context, MythVideoFrame *Frame, AVFrame *AvFrame)
323{
324 if (AvFrame->format != AV_PIX_FMT_MEDIACODEC)
325 return false;
327 return GetBuffer2(Context, Frame, AvFrame, 0);
328 return false;
329}
330
331AVPixelFormat MythMediaCodecContext::GetFormat(AVCodecContext* /*Context*/, const AVPixelFormat *PixFmt)
332{
333 while (*PixFmt != AV_PIX_FMT_NONE)
334 {
335 if (*PixFmt == AV_PIX_FMT_MEDIACODEC)
336 return AV_PIX_FMT_MEDIACODEC;
337 PixFmt++;
338 }
339 return AV_PIX_FMT_NONE;
340}
341
347{
348 if (!Frame)
349 return;
350
351 Frame->m_deinterlaceInuse = DEINT_BASIC | DEINT_DRIVER;
352 Frame->m_deinterlaceInuse2x = false;
353 Frame->m_interlaced = false;
354 Frame->m_interlacedReverse = false;
355 Frame->m_topFieldFirst = false;
356 Frame->m_deinterlaceAllowed = DEINT_NONE;
357 Frame->m_alreadyDeinterlaced = true;
358}
359
364bool MythMediaCodecContext::IsDeinterlacing(bool &DoubleRate, bool /*StreamChange*/)
365{
366 DoubleRate = true;
367 return true;
368}
369
371{
372 // TODO Something tells me this is leakier than a leaky thing
373 static QRecursiveMutex lock;
374 static bool s_initialised = false;
375 static MCProfiles s_profiles;
376
377 QMutexLocker locker(&lock);
378 if (s_initialised)
379 return s_profiles;
380 s_initialised = true;
381
382 static const std::vector< QPair<QString,QPair<MythCodecContext::CodecProfile, QList<int> > > > mimetypes =
383 {
384 { "video/mpeg2", { MythCodecContext::MPEG2,
387 { "video/mp4v-es", { MythCodecContext::MPEG4,
394 { "video/avc", { MythCodecContext::H264,
398 { "video/hevc", { MythCodecContext::HEVC,
401 { "video/x-vnd.on2.vp8", { MythCodecContext::VP8, { MC_VP8_MAIN }}},
402 { "video/x-vnd.on2.vp9", { MythCodecContext::VP9,
405 //{ "video/vc1", { MythCodecContext::VC1 , {}}}, // No FFmpeg support
406 //{ "video/3gpp", { MythCodecContext::H263 , {}}}, // No FFmpeg support
407 //{ "video/av01", { MythCodecContext::AV1 , {}}} // No FFmpeg support, API Level 29
408 };
409
410 // Retrieve MediaCodecList
412 QAndroidJniObject list("android/media/MediaCodecList", "(I)V", 0); // 0 = REGULAR_CODECS
413 if (!list.isValid())
414 return s_profiles;
415 // Retrieve array of MediaCodecInfo's
416 QAndroidJniObject qtcodecs = list.callObjectMethod("getCodecInfos", "()[Landroid/media/MediaCodecInfo;");
417 if (!qtcodecs.isValid())
418 return s_profiles;
419
420 // Iterate over MediaCodecInfo's
421 auto *codecs = qtcodecs.object<jobjectArray>();
422 jsize codeccount = env->GetArrayLength(codecs);
423 for (jsize i = 0; i < codeccount; ++i)
424 {
425 QAndroidJniObject codec(env->GetObjectArrayElement(codecs, i));
426 if (!codec.isValid())
427 continue;
428
429 // Ignore encoders
430 if (codec.callMethod<jboolean>("isEncoder"))
431 continue;
432
433 // Ignore software decoders
434 QString name = codec.callObjectMethod<jstring>("getName").toString();
435 if (name.contains("OMX.google", Qt::CaseInsensitive))
436 continue;
437
438 // Retrieve supported mimetypes (there is usually just one)
439 QAndroidJniObject qttypes = codec.callObjectMethod("getSupportedTypes", "()[Ljava/lang/String;");
440 auto *types = qttypes.object<jobjectArray>();
441 jsize typecount = env->GetArrayLength(types);
442 for (jsize j = 0; j < typecount; ++j)
443 {
444 QAndroidJniObject type(env->GetObjectArrayElement(types, j));
445 if (!type.isValid())
446 continue;
447
448 // Match mimetype to types supported by FFmpeg
449 QString typestr = type.toString();
450 for (const auto& mimetype : mimetypes)
451 {
452 if (mimetype.first != typestr)
453 continue;
454
455 // Retrieve MediaCodecInfo.CodecCapabilities for mimetype
456 QAndroidJniObject caps = codec.callObjectMethod("getCapabilitiesForType",
457 "(Ljava/lang/String;)Landroid/media/MediaCodecInfo$CodecCapabilities;",
458 type.object<jstring>());
459 if (!caps.isValid())
460 continue;
461
462 // Retrieve MediaCodecInfo.VideoCapabilities from CodecCapabilities
463 QAndroidJniObject videocaps = caps.callObjectMethod("getVideoCapabilities",
464 "()Landroid/media/MediaCodecInfo$VideoCapabilities;");
465 QAndroidJniObject widthrange = videocaps.callObjectMethod("getSupportedWidths",
466 "()Landroid/util/Range;");
467 QAndroidJniObject heightrange = videocaps.callObjectMethod("getSupportedHeights",
468 "()Landroid/util/Range;");
469
470 QAndroidJniObject widthqt = widthrange.callObjectMethod("getUpper", "()Ljava/lang/Comparable;");
471 QAndroidJniObject heightqt = heightrange.callObjectMethod("getUpper", "()Ljava/lang/Comparable;");
472 int width = widthqt.callMethod<jint>("intValue", "()I");
473 int height = heightqt.callMethod<jint>("intValue", "()I");
474
475 // Profiles are available from CodecCapabilities.profileLevel field
476 QAndroidJniObject profiles = caps.getObjectField("profileLevels",
477 "[Landroid/media/MediaCodecInfo$CodecProfileLevel;");
478 auto *profilearr = profiles.object<jobjectArray>();
479 jsize profilecount = env->GetArrayLength(profilearr);
480 if (profilecount < 1)
481 {
482 s_profiles.append(QPair<MythCodecContext::CodecProfile,QSize>(mimetype.second.first, QSize(width, height)));
483 continue;
484 }
485
486 for (jsize k = 0; k < profilecount; ++k)
487 {
488 jobject profile = env->GetObjectArrayElement(profilearr, k);
489 jclass objclass = env->GetObjectClass(profile);
490 jfieldID id = env->GetFieldID(objclass, "profile", "I");
491 int value = static_cast<int>(env->GetIntField(profile, id));
492 const QList<int>& mcprofiles = mimetype.second.second;
493 auto sameprof = [value](auto mcprofile) { return value == mcprofile; };
494 if (std::ranges::any_of(mcprofiles, sameprof))
495 {
496 MythCodecContext::CodecProfile p = MediaCodecToMythProfile(mimetype.second.first, value);
497 s_profiles.append(QPair<MythCodecContext::CodecProfile,QSize>(p, QSize(width, height)));
498 }
499 else
500 {
501 s_profiles.append(QPair<MythCodecContext::CodecProfile,QSize>(mimetype.second.first, QSize(width, height)));
502 }
503 }
504 }
505 }
506 }
507
508 return s_profiles;
509}
510
511void MythMediaCodecContext::GetDecoderList(QStringList &Decoders)
512{
514 if (profiles.isEmpty())
515 return;
516
517 Decoders.append("MediaCodec:");
518 for (auto profile : std::as_const(profiles))
519 Decoders.append(MythCodecContext::GetProfileDescription(profile.first, profile.second));
520}
521
522bool MythMediaCodecContext::HaveMediaCodec(bool Reinit /*=false*/)
523{
524 static QRecursiveMutex lock;
525 static bool s_initialised = false;
526 static bool s_available = false;
527
528 QMutexLocker locker(&lock);
529 if (!s_initialised || Reinit)
530 {
532 if (profiles.isEmpty())
533 {
534 LOG(VB_GENERAL, LOG_INFO, LOC + "No MediaCodec decoders found");
535 }
536 else
537 {
538 s_available = true;
539 LOG(VB_GENERAL, LOG_INFO, LOC + "Supported/available MediaCodec decoders:");
540 for (auto profile : std::as_const(profiles))
541 {
542 LOG(VB_GENERAL, LOG_INFO, LOC +
544 }
545 }
546 }
547 s_initialised = true;
548 return s_available;
549}
550
AVFrame AVFrame
A decoder for media files.
static void DeviceContextFinished(AVHWDeviceContext *Context)
static bool FrameTypeIsSupported(AVCodecContext *Context, VideoFrameType Format)
static int InitialiseDecoder2(AVCodecContext *Context, CreateHWDecoder Callback, const QString &Debug)
Initialise a hardware decoder that is NOT expected to use AVHWFramesContext.
virtual void InitVideoCodec(AVCodecContext *Context, bool SelectedStream, bool &DirectRendering)
static QString GetProfileDescription(CodecProfile Profile, QSize Size, VideoFrameType Format=FMT_NONE, uint ColorDepth=0)
static bool GetBuffer2(struct AVCodecContext *Context, MythVideoFrame *Frame, AVFrame *AvFrame, int Flags)
A generic hardware buffer initialisation method when AVHWFramesContext is NOT used.
static MythPlayerUI * GetPlayerUI(AVCodecContext *Context)
MythCodecID m_codecID
static CodecProfile FFmpegToMythProfile(AVCodecID CodecID, int Profile)
int HwDecoderInit(AVCodecContext *Context) override
static MythCodecID GetBestSupportedCodec(AVCodecContext **Context, const AVCodec **Codec, const QString &Decoder, AVStream *Stream, uint StreamType)
bool RetrieveFrame(AVCodecContext *Context, MythVideoFrame *Frame, AVFrame *AvFrame) override
void PostProcessFrame(AVCodecContext *, MythVideoFrame *Frame) override
Mark all MediaCodec decoded frames as progressive,.
MythMediaCodecContext(DecoderBase *Parent, MythCodecID CodecID)
static bool HaveMediaCodec(bool Reinit=false)
bool IsDeinterlacing(bool &DoubleRate, bool=false) override
static AVPixelFormat GetFormat(AVCodecContext *, const AVPixelFormat *PixFmt)
void InitVideoCodec(AVCodecContext *Context, bool SelectedStream, bool &DirectRendering) override
static void GetDecoderList(QStringList &Decoders)
static int InitialiseDecoder(AVCodecContext *Context)
static MCProfiles & GetProfiles(void)
static MythMediaCodecInterop * CreateMediaCodec(MythPlayerUI *Player, MythRenderOpenGL *Context, QSize Size)
unsigned int uint
Definition: compat.h:60
static const struct wl_interface * types[]
unsigned short uint16_t
Definition: iso6937tables.h:3
static bool codec_is_mediacodec_dec(MythCodecID id)
Definition: mythcodecid.h:340
MythCodecID
Definition: mythcodecid.h:14
@ kCodec_MPEG1_MEDIACODEC_DEC
Definition: mythcodecid.h:136
@ kCodec_MPEG1
Definition: mythcodecid.h:24
@ kCodec_MPEG1_MEDIACODEC
Definition: mythcodecid.h:120
static bool codec_is_mediacodec(MythCodecID id)
Definition: mythcodecid.h:337
static bool CODEC_IS_MEDIACODEC(const struct AVCodec *)
Definition: mythcodecid.h:416
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
@ DEINT_DRIVER
Definition: mythframe.h:74
@ DEINT_BASIC
Definition: mythframe.h:69
@ DEINT_NONE
Definition: mythframe.h:68
@ FMT_MEDIACODEC
Definition: mythframe.h:60
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define QAndroidJniEnvironment
#define LOC
MythCodecContext::CodecProfile MediaCodecToMythProfile(int Codec, int Profile)
@ MC_MPEG4_ADV_SCALEABLE
@ MC_MPEG4_BASIC_ANIMATED
@ MC_MPEG4_SIMPLE_FBA
@ MC_MPEG4_SCALEABLE_TEX
@ MC_MPEG4_ADV_CODING
@ MC_MPEG4_SIMPLE
@ MC_MPEG4_SIMPLE_SCALEABLE
@ MC_MPEG4_CORE_SCALEABLE
@ MC_MPEG4_ADV_SIMPLE
@ MC_MPEG4_ADV_CORE
@ MC_MPEG4_HYBRID
@ MC_MPEG4_ADV_REALTIME
@ MC_MPEG4_SIMPLE_FACE
@ MC_MPEG2_SPATIAL
@ MC_MPEG2_SIMPLE
@ MC_H264_CONST_HIGH
@ MC_H264_HIGH444
@ MC_H264_HIGH422
@ MC_H264_CONST_BASELINE
@ MC_H264_BASELINE
@ MC_H264_HIGH10
@ MC_H264_EXTENDED
@ MC_HEVC_MAIN_STILL
@ MC_HEVC_MAIN10HDR10
@ MC_HEVC_MMAIN10HDR10PLUS
@ MC_HEVC_MAIN10
@ MC_VP9_3HDRPLUS
@ MC_VP9_2HDRPLUS
#define QAndroidJniObject
QList< QPair< MythCodecContext::CodecProfile, QSize > > MCProfiles
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93