From e787df3ca92ccaf7cd71eed4128845e609f9f350 Mon Sep 17 00:00:00 2001 From: Portisch Date: Wed, 17 Jun 2026 12:51:27 +0200 Subject: [PATCH 001/375] VideoPlayer: fix aspect ratio for matroska containers for stereo modes Use the codec's width and height parameters for correct factor computation. --- xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index 292452a224e..a8c04453948 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -1476,9 +1476,9 @@ double CDVDDemuxFFmpeg::SelectAspect(AVStream* st, bool& forced) if (entry) { if (strcmp(entry->value, "left_right") == 0 || strcmp(entry->value, "right_left") == 0) - dar /= 2; + dar /= ((st->codecpar->width <= 1920.0 ? (st->codecpar->width * 2) : st->codecpar->width) / 1920.0); else if (strcmp(entry->value, "top_bottom") == 0 || strcmp(entry->value, "bottom_top") == 0) - dar *= 2; + dar *= ((st->codecpar->height <= 1080.0 ? (st->codecpar->height * 2) : st->codecpar->height) / 1080.0); } return dar; } From de1d0712a53a31bac563cb650e8b65a24f04831c Mon Sep 17 00:00:00 2001 From: Matthias Reichl Date: Tue, 16 Jun 2026 23:07:11 +0200 Subject: [PATCH 002/375] keymaps: map KEY_SELECT to Select action The OK button on lots of HID remotes generates KEY_SELECT, which gets mapped by kodi to the launch_media_select keyname. The historical default mapping of that key to open the music window is quite arbitrary, map it to the often used select action instead. Also add a longpress mapping to open the context menu, to match default KEY_ENTER and KEY_OK actions. Signed-off-by: Matthias Reichl --- system/keymaps/keyboard.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/keymaps/keyboard.xml b/system/keymaps/keyboard.xml index a5fb5ee8ca9..7aa91b0b95a 100644 --- a/system/keymaps/keyboard.xml +++ b/system/keymaps/keyboard.xml @@ -151,7 +151,8 @@ Rewind - ActivateWindow(Music) + Select + ContextMenu ActivateWindow(Programs) ActivateWindow(Programs) From bc4dad535cca2af845e4020966be40816ac6b09d Mon Sep 17 00:00:00 2001 From: Stefan Saraev Date: Sat, 18 Apr 2015 15:03:31 +0300 Subject: [PATCH 003/375] disable online check kodi-100.03-disable-online-check.patch --- xbmc/GUIInfoManager.cpp | 3 +-- xbmc/utils/SystemInfo.cpp | 5 +---- xbmc/windows/GUIWindowSystemInfo.cpp | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 05f8253831b..08a6c2d5f22 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -2024,7 +2024,7 @@ constexpr std::array weather = {{ ///

/// } // clang-format off -constexpr std::array system_labels = {{ +constexpr std::array system_labels = {{ {"hasnetwork", SYSTEM_ETHERNET_LINK_ACTIVE}, {"hasmediadvd", SYSTEM_MEDIA_DVD}, {"hasmediaaudiocd", SYSTEM_MEDIA_AUDIO_CD}, @@ -2072,7 +2072,6 @@ constexpr std::array system_labels = {{ {"currentcontrol", SYSTEM_CURRENT_CONTROL}, {"currentcontrolid", SYSTEM_CURRENT_CONTROL_ID}, {"dvdlabel", SYSTEM_DVD_LABEL}, - {"internetstate", SYSTEM_INTERNET_STATE}, {"osversioninfo", SYSTEM_OS_VERSION_INFO}, {"kernelversion", SYSTEM_OS_VERSION_INFO}, // old, not correct name {"uptime", SYSTEM_UPTIME}, diff --git a/xbmc/utils/SystemInfo.cpp b/xbmc/utils/SystemInfo.cpp index 13f070efba4..ed63ae1049e 100644 --- a/xbmc/utils/SystemInfo.cpp +++ b/xbmc/utils/SystemInfo.cpp @@ -280,7 +280,6 @@ bool CSysInfoJob::DoWork() { m_info.systemUptime = GetSystemUpTime(false); m_info.systemTotalUptime = GetSystemUpTime(true); - m_info.internetState = GetInternetState(); m_info.videoEncoder = GetVideoEncoder(); m_info.cpuFrequency = StringUtils::Format("{:4.0f} MHz", CServiceBroker::GetCPUInfo()->GetCPUFrequency()); @@ -1130,9 +1129,7 @@ int CSysInfo::GetXbmcBitness(void) bool CSysInfo::HasInternet() { - if (m_info.internetState != CSysData::UNKNOWN) - return m_info.internetState == CSysData::CONNECTED; - return (m_info.internetState = CSysInfoJob::GetInternetState()) == CSysData::CONNECTED; + return m_info.internetState == CSysData::UNKNOWN; } std::string CSysInfo::GetHddSpaceInfo(int drive, bool shortText) diff --git a/xbmc/windows/GUIWindowSystemInfo.cpp b/xbmc/windows/GUIWindowSystemInfo.cpp index 47f25d936c2..664e4bd2473 100644 --- a/xbmc/windows/GUIWindowSystemInfo.cpp +++ b/xbmc/windows/GUIWindowSystemInfo.cpp @@ -150,7 +150,6 @@ void CGUIWindowSystemInfo::FrameMove() SetControlLabel(i++, "{}: {}", 13160, NETWORK_GATEWAY_ADDRESS); SetControlLabel(i++, "{}: {}", 13161, NETWORK_DNS1_ADDRESS); SetControlLabel(i++, "{}: {}", 20307, NETWORK_DNS2_ADDRESS); - SetControlLabel(i++, "{} {}", 13295, SYSTEM_INTERNET_STATE); } else if (m_section == CONTROL_BT_VIDEO) From a1752aae09bb510b102e14878b3af797d7eb60e0 Mon Sep 17 00:00:00 2001 From: Portisch Date: Wed, 24 Sep 2025 12:43:35 +0200 Subject: [PATCH 004/375] [DVDDemuxFFMPEG] fix codec id check for mpegts Video stream is not allways at index 0. Fix commit https://github.com/xbmc/xbmc/pull/19983/commits/a49156bf13ff8a33146b4e79c8f2f52b47b28be3 --- .../DVDDemuxers/DVDDemuxFFmpeg.cpp | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index a8c04453948..b2b60c92480 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -504,14 +504,40 @@ bool CDVDDemuxFFmpeg::Open(const std::shared_ptr& pInput, bool if (isMpegTsWithStreams && !url.IsProtocol("tcp") && !fileinfo && !isBluray && !skipTsOptimization) { - av_opt_set_int(m_pFormatContext, "analyzeduration", 500000, 0); - m_checkTransportStream = true; - skipCreateStreams = true; + for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++) + { + if (m_pFormatContext->streams[i]) + { + if (m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + m_pFormatContext->streams[i]->codecpar->codec_id != AV_CODEC_ID_HEVC) + { + av_opt_set_int(m_pFormatContext, "analyzeduration", 500000, 0); + m_checkTransportStream = true; + skipCreateStreams = true; + break; + } + } + } } else if (!iformat || strcmp(iformat->name, "mpegts") != 0 || skipTsOptimization) { m_streaminfo = true; } + else if ((strcmp(iformat->name, "mpegts") == 0) && m_pFormatContext->nb_streams > 0) + { + for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++) + { + if (m_pFormatContext->streams[i]) + { + if (m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + m_pFormatContext->streams[i]->codecpar->codec_id == AV_CODEC_ID_HEVC) + { + m_streaminfo = true; + break; + } + } + } + } // we need to know if this is matroska, avi or sup later m_bMatroska = strncmp(m_pFormatContext->iformat->name, "matroska", 8) == 0; // for "matroska.webm" @@ -543,7 +569,7 @@ bool CDVDDemuxFFmpeg::Open(const std::shared_ptr& pInput, bool return false; } } - CLog::Log(LOGDEBUG, "{} - av_find_stream_info finished", __FUNCTION__); + CLog::Log(LOGDEBUG, "{} - avformat_find_stream_info finished", __FUNCTION__); // print some extra information av_dump_format(m_pFormatContext, 0, CURL::GetRedacted(strFile).c_str(), 0); From 162ce909f708339be6e569ef65a48c243bca4e1d Mon Sep 17 00:00:00 2001 From: Portisch Date: Tue, 9 Sep 2025 15:21:11 +0200 Subject: [PATCH 005/375] [DVDDemuxFFMPEG]: VVC send codec extradata only on start --- xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index b2b60c92480..ae5687689ff 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -2480,7 +2480,7 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamVideoState() if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (idx == m_pkt.pkt.stream_index && m_pkt.pkt.dts != AV_NOPTS_VALUE && - st->codecpar->extradata) + (st->codecpar->extradata || (st->codecpar->codec_id == AV_CODEC_ID_VVC))) { if (!m_startTime) { @@ -2502,7 +2502,7 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamVideoState() if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (static_cast(i) == m_pkt.pkt.stream_index && m_pkt.pkt.dts != AV_NOPTS_VALUE && - st->codecpar->extradata) + (st->codecpar->extradata || (st->codecpar->codec_id == AV_CODEC_ID_VVC))) { if (!m_startTime) { From 0998d8758a1a157399242326d8c8b7768558e5e7 Mon Sep 17 00:00:00 2001 From: Portisch Date: Mon, 22 Sep 2025 16:38:40 +0200 Subject: [PATCH 006/375] BitstreamConverter: convert VVC to annexb when codec extra data is available --- xbmc/utils/BitstreamConverter.cpp | 223 ++++++++++++++++++++++++++++++ xbmc/utils/BitstreamConverter.h | 1 + 2 files changed, 224 insertions(+) diff --git a/xbmc/utils/BitstreamConverter.cpp b/xbmc/utils/BitstreamConverter.cpp index b4f06067fd5..74660ea2a9c 100644 --- a/xbmc/utils/BitstreamConverter.cpp +++ b/xbmc/utils/BitstreamConverter.cpp @@ -73,6 +73,42 @@ enum HEVC_NAL_UNSPEC63 = 63 // Dolby Vision EL }; +enum +{ + VVC_TRAIL_NUT = 0, + VVC_STSA_NUT = 1, + VVC_RADL_NUT = 2, + VVC_RASL_NUT = 3, + VVC_RSV_VCL_4 = 4, + VVC_RSV_VCL_5 = 5, + VVC_RSV_VCL_6 = 6, + VVC_IDR_W_RADL = 7, + VVC_IDR_N_LP = 8, + VVC_CRA_NUT = 9, + VVC_GDR_NUT = 10, + VVC_RSV_IRAP_11 = 11, + VVC_OPI_NUT = 12, + VVC_DCI_NUT = 13, + VVC_VPS_NUT = 14, + VVC_SPS_NUT = 15, + VVC_PPS_NUT = 16, + VVC_PREFIX_APS_NUT = 17, + VVC_SUFFIX_APS_NUT = 18, + VVC_PH_NUT = 19, + VVC_AUD_NUT = 20, + VVC_EOS_NUT = 21, + VVC_EOB_NUT = 22, + VVC_PREFIX_SEI_NUT = 23, + VVC_SUFFIX_SEI_NUT = 24, + VVC_FD_NUT = 25, + VVC_RSV_NVCL_26 = 26, + VVC_RSV_NVCL_27 = 27, + VVC_UNSPEC_28 = 28, + VVC_UNSPEC_29 = 29, + VVC_UNSPEC_30 = 30, + VVC_UNSPEC_31 = 31, +}; + enum { SEI_BUFFERING_PERIOD = 0, @@ -490,6 +526,23 @@ bool CBitstreamConverter::Open(enum AVCodecID codec, } return false; break; + case AV_CODEC_ID_VVC: + if (in_extradata && in_extradata[0] == 0xff && (in_extradata[1] & 0xf0) == 0x0) + { + std::string extradatastr; + CLog::Log(LOGINFO, "CBitstreamConverter::Open VVC, convert to annexb format"); + + m_extraData = FFmpegExtraData(in_extradata, in_extrasize); + m_convert_bytestream = + BitstreamConvertInitVVC(m_extraData.GetData(), m_extraData.GetSize()); + } + else + { + CLog::Log(LOGINFO, "CBitstreamConverter::Open VVC, no extra data"); + m_convert_bytestream = true; + } + return true; + break; default: return false; break; @@ -619,6 +672,57 @@ bool CBitstreamConverter::Convert(uint8_t* pData, int iSize) return true; } } + else if (m_codec == AV_CODEC_ID_VVC) + { + if (m_to_annexb) + { + int nal_stream_pos = 0; + + m_inputSize = iSize; + m_inputBuffer = pData; + + if (!m_start_decode) + { + uint32_t packet_format = AV_RB32(m_inputBuffer); + m_convert_bytestream = packet_format != 0x1 && packet_format != 0x100; + } + + while (nal_stream_pos < iSize) + { + if (m_convert_bytestream) + { + static const uint8_t nalu_header[4] = {0, 0, 0, 1}; + uint32_t unit_size = AV_RB32(m_inputBuffer + nal_stream_pos) + 4; + uint16_t unit_type = (AV_RB16(m_inputBuffer + nal_stream_pos + 4) >> 3) & 0x1f; + + if (unit_type == VVC_SPS_NUT || IsIDR(unit_type)) + m_start_decode = true; + + memcpy(m_inputBuffer + nal_stream_pos, nalu_header, 4); + nal_stream_pos += unit_size; + } + else if (!m_start_decode) + { + uint8_t* buf = m_inputBuffer + nal_stream_pos; + + if (buf[0] == 0x0 && buf[1] == 0x0 && buf[2] == 0x1) + { + uint16_t unit_type = (AV_RB16(m_inputBuffer + nal_stream_pos + 3) >> 3) & 0x1f; + + if (unit_type == VVC_SPS_NUT || IsIDR(unit_type)) + m_start_decode = true; + + nal_stream_pos += 5; + } + else + nal_stream_pos++; + } + else + break; + } + } + return true; + } } return false; @@ -837,6 +941,122 @@ bool CBitstreamConverter::BitstreamConvertInitHEVC(void* in_extradata, int in_ex return true; } +bool CBitstreamConverter::BitstreamConvertInitVVC(void* in_extradata, int in_extrasize) +{ + m_sps_pps_size = 0; + m_sps_pps_context.sps_pps_data = NULL; + + // nothing to filter + if (!in_extradata) + return false; + + uint16_t unit_size; + uint32_t total_size = 0; + uint8_t *out = NULL, array_nb, nal_type, sps_seen = 0, pps_seen = 0; + uint8_t num_sublayers, num_bytes_constraint_info, ptl_sublayer_level_present_flags; + const uint8_t* extradata = (uint8_t*)in_extradata; + static const uint8_t nalu_header[4] = {0, 0, 0, 1}; + + // length coded size + m_sps_pps_context.length_size = sizeof(nalu_header); + + // skip several fields of VVCDecoderConfigurationRecord + // extradata point to 00 after FF, 8b + extradata++; + // ols_idx, num_sublayers , constant_frame_rate, chroma_format_idc, 16b + num_sublayers = ((extradata[0] << 8 | extradata[1]) >> 4) & 0x7; + extradata += 2; + // bit_depth_minus8, 8b + extradata++; + // num_bytes_constraint_info, 8b + num_bytes_constraint_info = extradata[0] & 0x3f; + extradata++; + // general_profile_idc, general_tier_flag, 8b + // general_level_idc, 8b + extradata += 2; + // constraint_info, 8b * num_bytes_constraint_info + extradata += num_bytes_constraint_info; + // ptl_sublayer_level_present_flag, 8b + ptl_sublayer_level_present_flags = *extradata++ & 0x3f; + for (int i = num_sublayers - 2; i >= 0; i--) + if ((ptl_sublayer_level_present_flags >> i) & 0x1) + extradata++; + // ptl_num_sub_profiles, 8b + extradata++; + // max_picture_width, 16b + extradata += 2; + // max_picture_height, 16b + extradata += 2; + // avg_frame_rate, 16b + extradata += 2; + // num_of_arrays, 8b + array_nb = *extradata++; + + while (array_nb--) + { + void* tmp; + + extradata++; // array_completeness, 8b + if (extradata[0] == 0x0 && extradata[1] == 0x01) + { + extradata += 2; // nal header, 16b + unit_size = extradata[0] << 8 | extradata[1]; + extradata += 2; // nal unit size, 16b + nal_type = (extradata[1] >> 3) & 0x1f; + + if (nal_type == VVC_SPS_NUT) + { + sps_seen = 1; + m_start_decode = true; + } + else if (nal_type == VVC_PPS_NUT) + { + pps_seen = 1; + } + else + { + extradata += unit_size; + continue; + } + total_size += unit_size + 4; + + if (total_size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE || + (extradata + unit_size) > ((uint8_t*)in_extradata + in_extrasize)) + { + av_free(out); + return false; + } + tmp = av_realloc(out, total_size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!tmp) + { + av_free(out); + return false; + } + out = (uint8_t*)tmp; + memcpy(out + total_size - unit_size - 4, nalu_header, 4); + memcpy(out + total_size - unit_size, extradata, unit_size); + extradata += unit_size; + } + else + return false; + } + + if (out) + memset(out + total_size, 0, AV_INPUT_BUFFER_PADDING_SIZE); + + if (!sps_seen) + CLog::Log(LOGDEBUG, "SPS NALU missing or invalid. The resulting stream may not play"); + if (!pps_seen) + CLog::Log(LOGDEBUG, "PPS NALU missing or invalid. The resulting stream may not play"); + + m_sps_pps_context.sps_pps_data = out; + m_sps_pps_context.size = total_size; + m_sps_pps_context.first_idr = 1; + m_sps_pps_context.idr_sps_pps_seen = 0; + + return true; +} + bool CBitstreamConverter::IsIDR(uint8_t unit_type) { switch (m_codec) @@ -846,6 +1066,9 @@ bool CBitstreamConverter::IsIDR(uint8_t unit_type) case AV_CODEC_ID_HEVC: return unit_type == HEVC_NAL_IDR_W_RADL || unit_type == HEVC_NAL_IDR_N_LP || unit_type == HEVC_NAL_CRA_NUT; + case AV_CODEC_ID_VVC: + return unit_type == VVC_IDR_W_RADL || unit_type == VVC_IDR_N_LP || + unit_type == VVC_CRA_NUT; default: return false; } diff --git a/xbmc/utils/BitstreamConverter.h b/xbmc/utils/BitstreamConverter.h index ae7ea0cd54f..8d94eb5b427 100644 --- a/xbmc/utils/BitstreamConverter.h +++ b/xbmc/utils/BitstreamConverter.h @@ -122,6 +122,7 @@ class CBitstreamConverter bool IsSlice(uint8_t unit_type); bool BitstreamConvertInitAVC(void* in_extradata, int in_extrasize); bool BitstreamConvertInitHEVC(void* in_extradata, int in_extrasize); + bool BitstreamConvertInitVVC(void* in_extradata, int in_extrasize); bool BitstreamConvert(uint8_t* pData, int iSize, uint8_t** poutbuf, int* poutbuf_size); static void BitstreamAllocAndCopy(uint8_t** poutbuf, int* poutbuf_size, From cbd6db8bd90b3e6e2b1c0689c1cdea865d5fc337 Mon Sep 17 00:00:00 2001 From: MilhouseVH Date: Thu, 5 Apr 2018 11:42:13 +0100 Subject: [PATCH 007/375] use udevil to mount kodi-100.04-use-udevil-to-umount.patch --- xbmc/platform/linux/storage/UDevProvider.cpp | 2 +- xbmc/platform/posix/PosixMountProvider.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/platform/linux/storage/UDevProvider.cpp b/xbmc/platform/linux/storage/UDevProvider.cpp index 1faba07352a..4ed3081156a 100644 --- a/xbmc/platform/linux/storage/UDevProvider.cpp +++ b/xbmc/platform/linux/storage/UDevProvider.cpp @@ -205,7 +205,7 @@ bool CUDevProvider::Eject(const std::string& mountpath) { // just go ahead and try to umount the disk // if it does umount, life is good, if not, no loss. - std::string cmd = "umount \"" + mountpath + "\""; + std::string cmd = "udevil umount \"" + mountpath + "\""; int status = system(cmd.c_str()); if (status == 0) diff --git a/xbmc/platform/posix/PosixMountProvider.cpp b/xbmc/platform/posix/PosixMountProvider.cpp index 25ec0896a60..d3f0682196b 100644 --- a/xbmc/platform/posix/PosixMountProvider.cpp +++ b/xbmc/platform/posix/PosixMountProvider.cpp @@ -124,7 +124,7 @@ bool CPosixMountProvider::Eject(const std::string& mountpath) #if !defined(TARGET_DARWIN_EMBEDDED) // just go ahead and try to umount the disk // if it does umount, life is good, if not, no loss. - std::string cmd = "umount \"" + mountpath + "\""; + std::string cmd = "udevil umount \"" + mountpath + "\""; int status = system(cmd.c_str()); if (status == 0) From 8f5e7ef3e8436b3de556e4fb902faa6777de603c Mon Sep 17 00:00:00 2001 From: MilhouseVH Date: Wed, 5 Jul 2017 15:46:51 +0100 Subject: [PATCH 008/375] make binary addons executable add executable mode to all files in addon's bin folder credits to vpeter4 for the patch kodi-100.05-make-binary-addons-executable.patch --- xbmc/addons/Addon.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/xbmc/addons/Addon.cpp b/xbmc/addons/Addon.cpp index c8b928be95c..641d63aee45 100644 --- a/xbmc/addons/Addon.cpp +++ b/xbmc/addons/Addon.cpp @@ -29,6 +29,10 @@ #include #include +#include +#include +#include + #ifdef HAS_PYTHON #include "interfaces/python/XBPython.h" #endif @@ -662,6 +666,28 @@ void OnPreInstall(const AddonPtr& addon) void OnPostInstall(const AddonPtr& addon, bool update, bool modal) { + // OE: make binary addons executable, creddits to vpeter4 + std::string addonDirPath; + std::string chmodFilePath; + DIR *addonsDir; + struct dirent *fileDirent; + struct stat fileStat; + int statRet; + + addonDirPath = "/storage/.kodi/addons/" + addon->ID() + "/bin/"; + if ((addonsDir = opendir(addonDirPath.c_str())) != NULL) + { + while ((fileDirent = readdir(addonsDir)) != NULL) + { + chmodFilePath = addonDirPath + fileDirent->d_name; + statRet = stat(chmodFilePath.c_str(), &fileStat); + if (statRet == 0 && (fileStat.st_mode & S_IFMT) != S_IFDIR) + chmod(chmodFilePath.c_str(), fileStat.st_mode | S_IXUSR | S_IXGRP | S_IXOTH); + } + closedir(addonsDir); + } + // OE + addon->OnPostInstall(update, modal); } From 3a8a7055af49bab028cd1e5114f5e441d6d54556 Mon Sep 17 00:00:00 2001 From: Stefan Saraev Date: Wed, 11 Mar 2015 20:56:15 +0200 Subject: [PATCH 009/375] dont set _NET_WM_STATE_FULLSCREEN I dont remember why I added this. I guess it was for some 3rdparty stuff that needed to go fullscreen but xbmc was acting weird.. this should probably be removed after OE 6 kodi-100.06-dont-set-_NET_WM_STATE_FULLSCREEN.patch --- xbmc/windowing/X11/WinSystemX11.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/xbmc/windowing/X11/WinSystemX11.cpp b/xbmc/windowing/X11/WinSystemX11.cpp index 7dd25263233..6ad155fc545 100644 --- a/xbmc/windowing/X11/WinSystemX11.cpp +++ b/xbmc/windowing/X11/WinSystemX11.cpp @@ -763,8 +763,6 @@ bool CWinSystemX11::SetWindow(int width, int height, bool fullscreen, const std: if (fullscreen && hasWM) { - Atom fs = XInternAtom(m_dpy, "_NET_WM_STATE_FULLSCREEN", True); - XChangeProperty(m_dpy, m_mainWindow, XInternAtom(m_dpy, "_NET_WM_STATE", True), XA_ATOM, 32, PropModeReplace, (unsigned char *) &fs, 1); // disable desktop compositing for KDE, when Kodi is in full-screen mode long one = 1; Atom composite = XInternAtom(m_dpy, "_KDE_NET_WM_BLOCK_COMPOSITING", True); From 5977772b64429bf354c227cb6ac0c8137fcbf0f6 Mon Sep 17 00:00:00 2001 From: Stefan Saraev Date: Sat, 18 Apr 2015 14:59:29 +0300 Subject: [PATCH 010/375] disable minimize kodi-100.07-disable-minimize.patch --- xbmc/application/ApplicationMessageHandling.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/xbmc/application/ApplicationMessageHandling.cpp b/xbmc/application/ApplicationMessageHandling.cpp index 275064fc692..e8cf95eb187 100644 --- a/xbmc/application/ApplicationMessageHandling.cpp +++ b/xbmc/application/ApplicationMessageHandling.cpp @@ -259,7 +259,6 @@ void CApplicationMessageHandling::OnApplicationMessage(MESSAGING::ThreadMessage* break; case TMSG_MINIMIZE: - CServiceBroker::GetWinSystem()->Minimize(); break; case TMSG_EXECUTE_OS: From 2c04d487f0995fccadcc3164a09791af517bb57a Mon Sep 17 00:00:00 2001 From: Matthias Reichl Date: Tue, 5 Sep 2017 09:17:30 +0200 Subject: [PATCH 011/375] kodi: refactor service addon wrapper Simplify the kodi addon wrapper patch, pass call hook context and addon path on to wrapper instead of just enable/disable. The logic of what should happen with service addons on enable/disable/install/uninstall is moved out of kodi into the wrapper script. Also add restart and reload contexts so service addons can call the wrapper from python code on settings change instead of having to directly interact with systemctl. Signed-off-by: Matthias Reichl kodi-100.09-use-a-wrapper-to-setup-service-addons.patch --- xbmc/addons/Addon.cpp | 38 ++++++++++++++++++++++++++++++++++++ xbmc/addons/AddonManager.cpp | 2 ++ xbmc/addons/IAddon.h | 9 +++++++++ 3 files changed, 49 insertions(+) diff --git a/xbmc/addons/Addon.cpp b/xbmc/addons/Addon.cpp index 641d63aee45..c019e78337a 100644 --- a/xbmc/addons/Addon.cpp +++ b/xbmc/addons/Addon.cpp @@ -657,6 +657,37 @@ CAddonVersion CAddon::GetDependencyVersion(const std::string& dependencyID) cons return m_addonInfo->DependencyVersion(dependencyID); } +void LEAddonHook(const AddonPtr& addon, const LE_ADDON_CONTEXT context) { + + if (addon->Type() == ADDON::AddonType::SERVICE) { + std::string contextStr; + char cmd[255]; + + switch (context) { + case LE_ADDON_ENABLED: + contextStr = "enable"; + break; + case LE_ADDON_DISABLED: + contextStr = "disable"; + break; + case LE_ADDON_POST_UPDATE: + contextStr = "post-update"; + break; + case LE_ADDON_PRE_UNINSTALL: + contextStr = "pre-uninstall"; + break; + default: + contextStr = StringUtils::Format("unknown({})", static_cast(context)); + break; + } + + snprintf(cmd, sizeof(cmd), "/usr/sbin/service-addon-wrapper %s %s %s", + contextStr.c_str(), addon->ID().c_str(), addon->Path().c_str()); + + system(cmd); + } +} + void OnPreInstall(const AddonPtr& addon) { //Fallback to the pre-install callback in the addon. @@ -688,11 +719,18 @@ void OnPostInstall(const AddonPtr& addon, bool update, bool modal) } // OE + if (update) + LEAddonHook(addon, LE_ADDON_POST_UPDATE); + else + LEAddonHook(addon, LE_ADDON_ENABLED); + addon->OnPostInstall(update, modal); } void OnPreUnInstall(const AddonPtr& addon) { + LEAddonHook(addon, LE_ADDON_PRE_UNINSTALL); + addon->OnPreUnInstall(); } diff --git a/xbmc/addons/AddonManager.cpp b/xbmc/addons/AddonManager.cpp index a26b227fd5b..30bfa8aeaf1 100644 --- a/xbmc/addons/AddonManager.cpp +++ b/xbmc/addons/AddonManager.cpp @@ -857,6 +857,7 @@ bool CAddonMgr::DisableAddon(const std::string& id, AddonDisabledReason disabled AddonPtr addon; if (GetAddon(id, addon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_NO) && addon != nullptr) { + ADDON::LEAddonHook(addon, ADDON::LE_ADDON_DISABLED); auto eventLog = CServiceBroker::GetEventLog(); if (eventLog) eventLog->Add(std::make_shared(addon, 24141)); @@ -908,6 +909,7 @@ bool CAddonMgr::EnableSingle(const std::string& id) if (!m_database->EnableAddon(id)) return false; m_disabled.erase(id); + ADDON::LEAddonHook(addon, ADDON::LE_ADDON_ENABLED); // If enabling a repo add-on without an origin, set its origin to its own id if (addon->HasType(AddonType::REPOSITORY) && addon->Origin().empty()) diff --git a/xbmc/addons/IAddon.h b/xbmc/addons/IAddon.h index c09ba97928a..a30eaae0bd3 100644 --- a/xbmc/addons/IAddon.h +++ b/xbmc/addons/IAddon.h @@ -69,6 +69,15 @@ using AddonPtr = std::shared_ptr; using VECADDONS = std::vector; using InfoMap = std::map>; +typedef enum { + LE_ADDON_ENABLED, + LE_ADDON_DISABLED, + LE_ADDON_POST_UPDATE, + LE_ADDON_PRE_UNINSTALL, +} LE_ADDON_CONTEXT; + +void LEAddonHook(const AddonPtr& addon, const LE_ADDON_CONTEXT context); + class IAddon : public std::enable_shared_from_this { public: From ac2654f0c4ee8f4f602636ad815248d14288df85 Mon Sep 17 00:00:00 2001 From: Stefan Saraev Date: Sat, 18 Apr 2015 15:45:54 +0300 Subject: [PATCH 012/375] prevent kodi switching to windowed mode kodi-100.12-prevent-kodi-switching-to-windowed-mode.patch --- xbmc/windowing/X11/WinSystemX11.h | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/windowing/X11/WinSystemX11.h b/xbmc/windowing/X11/WinSystemX11.h index f65538dd555..5a4bbb780ff 100644 --- a/xbmc/windowing/X11/WinSystemX11.h +++ b/xbmc/windowing/X11/WinSystemX11.h @@ -48,6 +48,7 @@ class CWinSystemX11 : public CWinSystemBase void FinishWindowResize(int newWidth, int newHeight) override; bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; void UpdateResolutions() override; + bool CanDoWindowed() override { return false; } void ShowOSMouse(bool show) override; void NotifyAppActiveChange(bool bActivated) override; From b59721038a84f014abc5e8e5b7d9cab09dac7b6f Mon Sep 17 00:00:00 2001 From: MilhouseVH Date: Thu, 5 Apr 2018 11:42:48 +0100 Subject: [PATCH 013/375] udevprovider: filter out OE specific mounts kodi-100.13-udevprovider-filter-out-OE-specific-mounts.patch --- xbmc/platform/linux/storage/UDevProvider.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/xbmc/platform/linux/storage/UDevProvider.cpp b/xbmc/platform/linux/storage/UDevProvider.cpp index 4ed3081156a..539b2e53aa4 100644 --- a/xbmc/platform/linux/storage/UDevProvider.cpp +++ b/xbmc/platform/linux/storage/UDevProvider.cpp @@ -150,6 +150,20 @@ void CUDevProvider::GetDisks(std::vector& disks, bool removable) continue; } + // LE: filter out Libreelec specific mounts + if (strstr(mountpoint, "/flash")) + { + udev_device_unref(device); + continue; + } + + if (strstr(mountpoint, "/storage")) + { + udev_device_unref(device); + continue; + } + // OE + // look for devices on the usb bus, or mounted on */media/ (sdcards), or optical devices const char *bus = udev_device_get_property_value(device, "ID_BUS"); const char *optical = udev_device_get_property_value(device, "ID_CDROM"); // matches also DVD, Blu-ray From 20f9ed9acaa965adaf69c7ee645ba2eef909a406 Mon Sep 17 00:00:00 2001 From: fritsch Date: Fri, 29 Jan 2016 16:32:06 +0100 Subject: [PATCH 014/375] change pulseaudio default device name to Bluetooth Audio kodi-100.15-rename-default-pulse-device.patch --- xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp index 5965c76a962..875a359704d 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp @@ -510,7 +510,7 @@ static void SinkInfoRequestCallback(pa_context *c, const pa_sink_info *i, int eo CAEDeviceInfo defaultDevice; defaultDevice.m_deviceName = std::string("Default"); defaultDevice.m_displayName = std::string("Default"); - defaultDevice.m_displayNameExtra = std::string("Default Output Device (PULSEAUDIO)"); + defaultDevice.m_displayNameExtra = std::string("Bluetooth Audio (PULSEAUDIO)"); defaultDevice.m_dataFormats.insert(defaultDevice.m_dataFormats.end(), defaultDataFormats.begin(), defaultDataFormats.end()); defaultDevice.m_channels = CAEChannelInfo(AE_CH_LAYOUT_2_0); @@ -518,6 +518,8 @@ static void SinkInfoRequestCallback(pa_context *c, const pa_sink_info *i, int eo defaultDevice.m_deviceType = AE_DEVTYPE_PCM; defaultDevice.m_wantsIECPassthrough = true; sinkStruct->list->push_back(defaultDevice); + // OE only wants the default device - so we are done here + return; } if (i && i->name) { From be7a0260cb8155b9bcad947425cef2454365361b Mon Sep 17 00:00:00 2001 From: chewitt Date: Sat, 30 Sep 2017 12:58:29 +0100 Subject: [PATCH 015/375] update master with 8.2 changes kodi-100.16-remove-sysinfo-battery.patch --- xbmc/windows/GUIWindowSystemInfo.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/xbmc/windows/GUIWindowSystemInfo.cpp b/xbmc/windows/GUIWindowSystemInfo.cpp index 664e4bd2473..05591399c07 100644 --- a/xbmc/windows/GUIWindowSystemInfo.cpp +++ b/xbmc/windows/GUIWindowSystemInfo.cpp @@ -124,7 +124,6 @@ void CGUIWindowSystemInfo::FrameMove() SetControlLabel(i++, "{} {}", 13283, SYSTEM_OS_VERSION_INFO); SetControlLabel(i++, "{}: {}", 12390, SYSTEM_UPTIME); SetControlLabel(i++, "{}: {}", 12394, SYSTEM_TOTALUPTIME); - SetControlLabel(i++, "{}: {}", 12395, SYSTEM_BATTERY_LEVEL); } else if (m_section == CONTROL_BT_STORAGE) From ecaa7c8d61d145b5cad7f14f578377612bfb7180 Mon Sep 17 00:00:00 2001 From: MilhouseVH Date: Tue, 17 Dec 2019 15:51:23 +0000 Subject: [PATCH 016/375] refresh /etc/environment on install and uninstall kodi-100.30-addons-refresh-etc-environment.patch --- xbmc/addons/Addon.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xbmc/addons/Addon.cpp b/xbmc/addons/Addon.cpp index c019e78337a..1d8462db681 100644 --- a/xbmc/addons/Addon.cpp +++ b/xbmc/addons/Addon.cpp @@ -725,6 +725,9 @@ void OnPostInstall(const AddonPtr& addon, bool update, bool modal) LEAddonHook(addon, LE_ADDON_ENABLED); addon->OnPostInstall(update, modal); + + if (addon->Type() == ADDON::AddonType::SERVICE || addon->Type() == ADDON::AddonType::SCRIPT) + system("/usr/bin/environment-setup"); } void OnPreUnInstall(const AddonPtr& addon) @@ -737,6 +740,9 @@ void OnPreUnInstall(const AddonPtr& addon) void OnPostUnInstall(const AddonPtr& addon) { addon->OnPostUnInstall(); + + if (addon->Type() == ADDON::AddonType::SERVICE || addon->Type() == ADDON::AddonType::SCRIPT) + system("/usr/bin/environment-setup"); } } // namespace ADDON From f01b62ab8136c321af861faeee58d712bf5ec4d7 Mon Sep 17 00:00:00 2001 From: Matthias Reichl Date: Sun, 10 Feb 2019 12:04:50 +0100 Subject: [PATCH 017/375] kodi: cleanup Lircmap.xml patch Drop KEY_TITLE mapping, this is already mapped in kodi Lircmap.xml. Drop mappings with invalid keycodes KEY_FFORWARD KEY_VOLUP KEY_VOLDOWN KEY_CHUP KEY_CHDOWN KEY_STAR KEY_POUND, they don't exist in linux/input-event-codes.h Signed-off-by: Matthias Reichl kodi-995.10-devinputmappings.patch --- system/Lircmap.xml | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/system/Lircmap.xml b/system/Lircmap.xml index a2a220ac2c0..5c24c58bd71 100644 --- a/system/Lircmap.xml +++ b/system/Lircmap.xml @@ -548,50 +548,85 @@ KEY_UP KEY_DOWN - KEY_ENTER + + + KEY_DELETE + KEY_ESC KEY_MEDIA + KEY_HOME KEY_EXIT + KEY_BACK + KEY_BACKSPACE + KEY_ESC + KEY_E KEY_RECORD KEY_PLAY + KEY_PLAYPAUSE KEY_PAUSE KEY_STOP + KEY_STOPCD KEY_FASTFORWARD + KEY_FORWARD KEY_REWIND KEY_VOLUMEUP KEY_VOLUMEDOWN KEY_CHANNELUP + KEY_PAGEUP KEY_CHANNELDOWN + KEY_PAGEDOWN KEY_NEXT + KEY_NEXTSONG KEY_PREVIOUS + KEY_PREVIOUSSONG KEY_EPG KEY_TITLE + KEY_TV2 + KEY_CONTEXT_MENU KEY_SUBTITLE KEY_LANGUAGE KEY_INFO + KEY_PROPS KEY_ZOOM + KEY_ANGLE KEY_MUTE KEY_POWER + KEY_SLEEP + KEY_WAKEUP KEY_EJECTCD KEY_EJECTCLOSECD

KEY_DVD KEY_MENU KEY_VIDEO KEY_AUDIO + KEY_MP3 KEY_CAMERA + KEY_IMAGES KEY_TUNER KEY_TV + KEY_PVR KEY_TEXT + KEY_1 KEY_NUMERIC_1 + KEY_2 KEY_NUMERIC_2 + KEY_3 KEY_NUMERIC_3 + KEY_4 KEY_NUMERIC_4 + KEY_5 KEY_NUMERIC_5 + KEY_6 KEY_NUMERIC_6 + KEY_7 KEY_NUMERIC_7 + KEY_8 KEY_NUMERIC_8 + KEY_9 KEY_NUMERIC_9 + KEY_0 KEY_NUMERIC_0 + KEY_KPASTERISK KEY_NUMERIC_STAR KEY_NUMERIC_POUND KEY_RED From 22535046a7eb45b6fbdecc6b715e8bb7ed497185 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Wed, 13 Apr 2016 11:09:49 +0200 Subject: [PATCH 018/375] kodi: add patch to disable using the tv menu language when using CEC kodi-999.15-disable-using-tv-menu-language-by-default.patch --- system/peripherals.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/peripherals.xml b/system/peripherals.xml index ebbb3c978bc..dd38eefc86f 100644 --- a/system/peripherals.xml +++ b/system/peripherals.xml @@ -19,7 +19,7 @@ - + From 952d4f177bd0f68403cb6476e9bc2b7575e5d93b Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Sun, 24 Apr 2022 21:04:04 +0200 Subject: [PATCH 019/375] kodi: Add host target for addon headers kodi-999.20-headers-only.patch --- CMakeLists.txt | 3 +++ cmake/scripts/linux/Install.cmake | 2 ++ 2 files changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92bd22cc27c..7cb47722b9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ include(CMakeDependentOption) include(cmake/scripts/common/DependencyOptions.cmake) # general +option(HEADERS_ONLY "Skip building" OFF) option(VERBOSE "Enable verbose output?" OFF) option(VERBOSE_FIND "Enable verbose output of library searches only?" OFF) option(ENABLE_CLANGTIDY "Enable clang-tidy support?" OFF) @@ -183,6 +184,7 @@ core_find_git_rev(APP_SCMID FULL) set(AUDIO_BACKENDS_LIST "" CACHE STRING "Available audio backends") set(GL_INTERFACES_LIST "" CACHE STRING "Available GL interfaces") +if(NOT HEADERS_ONLY) # Dynamically loaded libraries built with the project add_custom_target(${APP_NAME_LC}-libraries) set(LIBRARY_FILES "" CACHE STRING "" FORCE) @@ -673,6 +675,7 @@ if(FFMPEG_XCODE_WRAPPER) set(CMAKE_XCODE_ATTRIBUTE_LD "${FFMPEG_XCODE_WRAPPER}") set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS "${FFMPEG_XCODE_WRAPPER}") endif() +endif() # Platform specific additional extra targets if(EXISTS ${CMAKE_SOURCE_DIR}/cmake/scripts/${CORE_SYSTEM_NAME}/ExtraTargets.cmake) diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index 8a6c96fd36f..937fa94d10f 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -45,6 +45,7 @@ configure_file(${CMAKE_SOURCE_DIR}/tools/Linux/kodi.desktop.in configure_file(${CMAKE_SOURCE_DIR}/tools/Linux/kodi.metainfo.xml.in ${CORE_BUILD_DIR}/${APP_PACKAGE}.metainfo.xml @ONLY) +if(NOT HEADERS_ONLY) # Install app install(TARGETS ${APP_NAME_LC} DESTINATION ${libdir}/${APP_NAME_LC} @@ -164,6 +165,7 @@ if(INTERNAL_TEXTUREPACKER_INSTALLABLE) RENAME "${APP_NAME_LC}-TexturePacker" COMPONENT kodi-tools-texturepacker) endif() +endif() # Install kodi-addon-dev headers include(${CMAKE_SOURCE_DIR}/xbmc/addons/AddonBindings.cmake) From 2348f52e431653e038b3a7ba60a6ced8ca15b6fc Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 1 Jun 2020 17:53:33 +0200 Subject: [PATCH 020/375] Revert "Remove amlogic platform" This reverts commit 750b8c4b2408938ff0d0b26e732de4e6c8c692b0. --- .../resources/strings.po | 29 +- cmake/modules/FindAML.cmake | 49 + cmake/platform/linux/aml.cmake | 2 + cmake/scripts/android/ArchSetup.cmake | 1 + cmake/scripts/common/Platform.cmake | 4 +- cmake/treedata/common/aml/aml.txt | 2 + system/settings/settings.xml | 75 + tools/buildsteps/linux-aml/configure-depends | 9 + tools/buildsteps/linux-aml/configure-xbmc | 5 + tools/buildsteps/linux-aml/make-binary-addons | 28 + tools/buildsteps/linux-aml/make-depends | 8 + .../buildsteps/linux-aml/make-native-depends | 9 + tools/buildsteps/linux-aml/make-xbmc | 5 + tools/buildsteps/linux-aml/package | 5 + tools/buildsteps/linux-aml/prepare-depends | 15 + tools/buildsteps/linux-aml/prepare-xbmc | 9 + tools/depends/configure.ac | 2 +- tools/depends/target/Makefile | 3 + .../depends/target/libamcodec/01-flags.patch | 22 + .../target/libamcodec/02-no-amadec.patch | 11 + tools/depends/target/libamcodec/Makefile | 38 + .../ApplicationSettingsHandling.cpp | 1 + xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp | 10 + .../process/amlogic/CMakeLists.txt | 6 + .../process/amlogic/RPProcessInfoAmlogic.cpp | 48 + .../process/amlogic/RPProcessInfoAmlogic.h | 30 + .../VideoPlayer/DVDCodecs/Video/AMLCodec.cpp | 2422 +++++++++++++++++ .../VideoPlayer/DVDCodecs/Video/AMLCodec.h | 108 + .../DVDCodecs/Video/CMakeLists.txt | 7 + .../DVDCodecs/Video/DVDVideoCodecAmlogic.cpp | 457 ++++ .../DVDCodecs/Video/DVDVideoCodecAmlogic.h | 101 + .../VideoPlayer/VideoRenderers/CMakeLists.txt | 1 + .../VideoRenderers/HwDecRender/CMakeLists.txt | 5 + .../HwDecRender/RendererAML.cpp | 160 ++ .../VideoRenderers/HwDecRender/RendererAML.h | 56 + .../VideoShaders/CMakeLists.txt | 1 + xbmc/platform/linux/CMakeLists.txt | 5 + xbmc/platform/linux/ScreenshotSurfaceAML.cpp | 76 + xbmc/platform/linux/ScreenshotSurfaceAML.h | 23 + xbmc/platform/linux/input/CMakeLists.txt | 5 +- xbmc/settings/SettingConditions.cpp | 7 + xbmc/settings/Settings.h | 4 + xbmc/utils/AMLUtils.cpp | 702 +++++ xbmc/utils/AMLUtils.h | 67 + xbmc/utils/BitstreamConverter.cpp | 4 +- xbmc/utils/CMakeLists.txt | 7 + xbmc/utils/ScreenshotAML.cpp | 85 + xbmc/utils/ScreenshotAML.h | 17 + xbmc/utils/SystemInfo.cpp | 3 + xbmc/windowing/amlogic/CMakeLists.txt | 12 + xbmc/windowing/amlogic/VideoSyncAML.cpp | 95 + xbmc/windowing/amlogic/VideoSyncAML.h | 26 + xbmc/windowing/amlogic/WinSystemAmlogic.cpp | 263 ++ xbmc/windowing/amlogic/WinSystemAmlogic.h | 56 + .../amlogic/WinSystemAmlogicGLESContext.cpp | 153 ++ .../amlogic/WinSystemAmlogicGLESContext.h | 45 + 56 files changed, 5390 insertions(+), 9 deletions(-) create mode 100644 cmake/modules/FindAML.cmake create mode 100644 cmake/platform/linux/aml.cmake create mode 100644 cmake/treedata/common/aml/aml.txt create mode 100644 tools/buildsteps/linux-aml/configure-depends create mode 100644 tools/buildsteps/linux-aml/configure-xbmc create mode 100644 tools/buildsteps/linux-aml/make-binary-addons create mode 100644 tools/buildsteps/linux-aml/make-depends create mode 100644 tools/buildsteps/linux-aml/make-native-depends create mode 100644 tools/buildsteps/linux-aml/make-xbmc create mode 100644 tools/buildsteps/linux-aml/package create mode 100644 tools/buildsteps/linux-aml/prepare-depends create mode 100644 tools/buildsteps/linux-aml/prepare-xbmc create mode 100644 tools/depends/target/libamcodec/01-flags.patch create mode 100644 tools/depends/target/libamcodec/02-no-amadec.patch create mode 100644 tools/depends/target/libamcodec/Makefile create mode 100644 xbmc/cores/RetroPlayer/process/amlogic/CMakeLists.txt create mode 100644 xbmc/cores/RetroPlayer/process/amlogic/RPProcessInfoAmlogic.cpp create mode 100644 xbmc/cores/RetroPlayer/process/amlogic/RPProcessInfoAmlogic.h create mode 100644 xbmc/cores/VideoPlayer/DVDCodecs/Video/AMLCodec.cpp create mode 100644 xbmc/cores/VideoPlayer/DVDCodecs/Video/AMLCodec.h create mode 100644 xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.cpp create mode 100644 xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.h create mode 100644 xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererAML.cpp create mode 100644 xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererAML.h create mode 100644 xbmc/platform/linux/ScreenshotSurfaceAML.cpp create mode 100644 xbmc/platform/linux/ScreenshotSurfaceAML.h create mode 100644 xbmc/utils/AMLUtils.cpp create mode 100644 xbmc/utils/AMLUtils.h create mode 100644 xbmc/utils/ScreenshotAML.cpp create mode 100644 xbmc/utils/ScreenshotAML.h create mode 100644 xbmc/windowing/amlogic/CMakeLists.txt create mode 100644 xbmc/windowing/amlogic/VideoSyncAML.cpp create mode 100644 xbmc/windowing/amlogic/VideoSyncAML.h create mode 100644 xbmc/windowing/amlogic/WinSystemAmlogic.cpp create mode 100644 xbmc/windowing/amlogic/WinSystemAmlogic.h create mode 100644 xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp create mode 100644 xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 77a0d2d9841..46166dd2d8e 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -7561,7 +7561,7 @@ msgstr "" #: system/settings/settings.xml msgctxt "#13438" -msgid "Allow hardware acceleration with DRM PRIME" +msgid "Allow hardware acceleration - amcodec" msgstr "" #: system/settings/settings.xml @@ -21748,6 +21748,11 @@ msgstr "" #: system/settings/settings.xml msgctxt "#36439" msgid "Enable automatic playback of the next programme when playing past programmes (catchup) or for Video On Demand (VOD) channels. Please note that catchup and VOD only work if supported by the channel provider." + +#. Description of setting with label #13438 "Allow hardware acceleration (amcodec)" +#: system/settings/settings.xml +msgctxt "#36440" +msgid "Enable hardware video decode using Amlogic decoder." msgstr "" #. Description of setting with label #14085 "Video disc insert action" @@ -23778,7 +23783,27 @@ msgctxt "#38354" msgid "Unable to read xml file" msgstr "" -#empty strings from id 38355 to 39003 +#empty strings from id 38355 to 38999 + +#: system/settings/settings.xml +msgctxt "#39000" +msgid "HD and up" +msgstr "" + +#: system/settings/settings.xml +msgctxt "#39001" +msgid "Accelerate MPEG2" +msgstr "" + +#: system/settings/settings.xml +msgctxt "#39002" +msgid "Accelerate MPEG4" +msgstr "" + +#: system/settings/settings.xml +msgctxt "#39003" +msgid "Accelerate h264" +msgstr "" #. Description of category "Library" with label #14202 #: system/settings/settings.xml diff --git a/cmake/modules/FindAML.cmake b/cmake/modules/FindAML.cmake new file mode 100644 index 00000000000..1965e6f8472 --- /dev/null +++ b/cmake/modules/FindAML.cmake @@ -0,0 +1,49 @@ +#.rst: +# FindAML +# ------- +# Finds the AML codec +# +# This will define the following variables:: +# +# AML_INCLUDE_DIRS - the AML include directory +# AML_DEFINITIONS - the AML definitions +# +# and the following imported targets:: +# +# ${APP_NAME_LC}::AML - The AML codec + +find_path(AML_INCLUDE_DIR codec_error.h + PATH_SUFFIXES amcodec) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(AML + REQUIRED_VARS AML_INCLUDE_DIR) + +include(CheckCSourceCompiles) +set(CMAKE_REQUIRED_INCLUDES ${AML_INCLUDE_DIR}) +check_c_source_compiles("#include + + int main() + { + int i = VIDEO_DEC_FORMAT_VP9; + return 0; + } + " AML_HAS_VP9) + +if(AML_FOUND) + set(AML_INCLUDE_DIRS ${AML_INCLUDE_DIR}) + set(AML_DEFINITIONS -DHAS_LIBAMCODEC=1) + + if(AML_HAS_VP9) + list(APPEND AML_DEFINITIONS -DHAS_LIBAMCODEC_VP9=1) + endif() + + if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${AML_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS "${AML_DEFINITIONS}") + endif() +endif() + +mark_as_advanced(AMLCODEC_INCLUDE_DIR) diff --git a/cmake/platform/linux/aml.cmake b/cmake/platform/linux/aml.cmake new file mode 100644 index 00000000000..4206c283e23 --- /dev/null +++ b/cmake/platform/linux/aml.cmake @@ -0,0 +1,2 @@ +list(APPEND PLATFORM_REQUIRED_DEPS OpenGLES AML EGL LibInput Xkbcommon) +set(APP_RENDER_SYSTEM gles) diff --git a/cmake/scripts/android/ArchSetup.cmake b/cmake/scripts/android/ArchSetup.cmake index b6f23ff308e..76ea18c9f7d 100644 --- a/cmake/scripts/android/ArchSetup.cmake +++ b/cmake/scripts/android/ArchSetup.cmake @@ -34,6 +34,7 @@ endif() list(APPEND SYSTEM_DEFINES -DHAS_ZEROCONF) set(ENABLE_X11 OFF CACHE BOOL "" FORCE) +set(ENABLE_AML OFF CACHE BOOL "" FORCE) set(ENABLE_OPTICAL OFF CACHE BOOL "" FORCE) set(ENABLE_MDNS OFF CACHE BOOL "" FORCE) diff --git a/cmake/scripts/common/Platform.cmake b/cmake/scripts/common/Platform.cmake index 397d8d46e8c..7a4a35a31b5 100644 --- a/cmake/scripts/common/Platform.cmake +++ b/cmake/scripts/common/Platform.cmake @@ -3,9 +3,9 @@ if(NOT CORE_SYSTEM_NAME) endif() if(CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) - # Set default CORE_PLATFORM_NAME to X11 WAYLAND GBM + # Set default CORE_PLATFORM_NAME to X11 WAYLAND AML GBM # This is overridden by user setting -DCORE_PLATFORM_NAME= - set(_DEFAULT_PLATFORM X11 WAYLAND GBM) + set(_DEFAULT_PLATFORM X11 WAYLAND AML GBM) if(NOT APP_RENDER_SYSTEM) message(SEND_ERROR "You need to decide whether you want to use GL- or GLES-based rendering. Please set APP_RENDER_SYSTEM to either \"gl\" or \"gles\". For normal desktop systems, you will usually want to use \"gl\".") diff --git a/cmake/treedata/common/aml/aml.txt b/cmake/treedata/common/aml/aml.txt new file mode 100644 index 00000000000..1e4856fcd42 --- /dev/null +++ b/cmake/treedata/common/aml/aml.txt @@ -0,0 +1,2 @@ +xbmc/cores/RetroPlayer/process/amlogic cores/RetroPlayer/process/amlogic +xbmc/windowing/amlogic windowing/amlogic diff --git a/system/settings/settings.xml b/system/settings/settings.xml index 4928767d89f..22a85d65254 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -169,6 +169,81 @@ + + HAVE_AMCODEC + 2 + true + + + + + + + HAVE_AMCODEC + 2 + 0 + + + + + + + + + + + + + true + + + + + + + HAVE_AMCODEC + 2 + 800 + + + + + + + + + + + + + true + + + + + + + HAVE_AMCODEC + 2 + 0 + + + + + + + + + + + + + true + + + + + HAS_MEDIACODEC 2 diff --git a/tools/buildsteps/linux-aml/configure-depends b/tools/buildsteps/linux-aml/configure-depends new file mode 100644 index 00000000000..d8b798148df --- /dev/null +++ b/tools/buildsteps/linux-aml/configure-depends @@ -0,0 +1,9 @@ +WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} +XBMC_PLATFORM_DIR=linux-aml +. $WORKSPACE/tools/buildsteps/defaultenv + +if [ "$(pathChanged $WORKSPACE/tools/depends)" == "1" ] +then + cd $WORKSPACE/tools/depends;./configure \ + --with-toolchain=/usr --prefix=$XBMC_DEPENDS_ROOT --host=aarch64-linux-gnu --with-platform=aml --with-tarballs=$TARBALLS $DEBUG_SWITCH +fi diff --git a/tools/buildsteps/linux-aml/configure-xbmc b/tools/buildsteps/linux-aml/configure-xbmc new file mode 100644 index 00000000000..fb1f8b5e2b8 --- /dev/null +++ b/tools/buildsteps/linux-aml/configure-xbmc @@ -0,0 +1,5 @@ +WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} +XBMC_PLATFORM_DIR=linux-aml +. $WORKSPACE/tools/buildsteps/defaultenv + +make -C $WORKSPACE/tools/depends/target/cmakebuildsys diff --git a/tools/buildsteps/linux-aml/make-binary-addons b/tools/buildsteps/linux-aml/make-binary-addons new file mode 100644 index 00000000000..fe5b4f07eac --- /dev/null +++ b/tools/buildsteps/linux-aml/make-binary-addons @@ -0,0 +1,28 @@ +WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} +XBMC_PLATFORM_DIR=linux-aml +. $WORKSPACE/tools/buildsteps/defaultenv + +. $WORKSPACE/tools/buildsteps/$XBMC_PLATFORM_DIR/make-native-depends + +#clear the build failed file +rm -f $WORKSPACE/cmake/$FAILED_BUILD_FILENAME + +ALL_BINARY_ADDONS_BUILT="1" +#only build binary addons when requested by env/jenkins +if [ "$BUILD_BINARY_ADDONS" == "true" ] +then + for addon in $BINARY_ADDONS + do + echo "building $addon" + git clean -xffd $WORKSPACE/$BINARY_ADDONS_ROOT/$addon + cd $WORKSPACE/$BINARY_ADDONS_ROOT/$addon;make -j $BUILDTHREADS V=99 VERBOSE=1 || ALL_BINARY_ADDONS_BUILT="0" + done +fi + +if [ "$ALL_BINARY_ADDONS_BUILT" == "1" ] +then + tagSuccessFulBuild $WORKSPACE/cmake +else + #mark the build failure in the filesystem but leave jenkins running + tagFailedBuild $WORKSPACE/cmake +fi diff --git a/tools/buildsteps/linux-aml/make-depends b/tools/buildsteps/linux-aml/make-depends new file mode 100644 index 00000000000..6e2cc29a165 --- /dev/null +++ b/tools/buildsteps/linux-aml/make-depends @@ -0,0 +1,8 @@ +WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} +XBMC_PLATFORM_DIR=linux-aml +. $WORKSPACE/tools/buildsteps/defaultenv + +if [ "$(pathChanged $WORKSPACE/tools/depends)" == "1" ] +then + cd $WORKSPACE/tools/depends;make -j $BUILDTHREADS || make && tagSuccessFulBuild $WORKSPACE/tools/depends +fi diff --git a/tools/buildsteps/linux-aml/make-native-depends b/tools/buildsteps/linux-aml/make-native-depends new file mode 100644 index 00000000000..0ed2d5db08f --- /dev/null +++ b/tools/buildsteps/linux-aml/make-native-depends @@ -0,0 +1,9 @@ +WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} +XBMC_PLATFORM_DIR=linux-aml +. $WORKSPACE/tools/buildsteps/defaultenv + +if [ "$(pathChanged $WORKSPACE/tools/depends)" == "1" ] && [ "$BINARY_ADDONS_CLEAN_NATIVETOOLS" != "0" ] +then + git clean -xffd $WORKSPACE/tools/depends/native + cd $WORKSPACE/tools/depends/native;make -j $BUILDTHREADS && tagSuccessFulBuild $WORKSPACE/tools/depends +fi diff --git a/tools/buildsteps/linux-aml/make-xbmc b/tools/buildsteps/linux-aml/make-xbmc new file mode 100644 index 00000000000..b0a7fc5ee38 --- /dev/null +++ b/tools/buildsteps/linux-aml/make-xbmc @@ -0,0 +1,5 @@ +WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} +XBMC_PLATFORM_DIR=linux-aml +. $WORKSPACE/tools/buildsteps/defaultenv + +cd $WORKSPACE/build;make -j$BUILDTHREADS || make diff --git a/tools/buildsteps/linux-aml/package b/tools/buildsteps/linux-aml/package new file mode 100644 index 00000000000..17c6abf7ba2 --- /dev/null +++ b/tools/buildsteps/linux-aml/package @@ -0,0 +1,5 @@ +WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} +XBMC_PLATFORM_DIR=linux-aml +. $WORKSPACE/tools/buildsteps/defaultenv + +#nothing for linux atm diff --git a/tools/buildsteps/linux-aml/prepare-depends b/tools/buildsteps/linux-aml/prepare-depends new file mode 100644 index 00000000000..9daea8cf45b --- /dev/null +++ b/tools/buildsteps/linux-aml/prepare-depends @@ -0,0 +1,15 @@ +WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} +XBMC_PLATFORM_DIR=linux-aml +. $WORKSPACE/tools/buildsteps/defaultenv + +#clean without depends for skipping depends build if possible +#also skip binary addons (pvr, audioencoder) as long as they are deployed in tree +cd $WORKSPACE;git clean -xfd -e "cmake/.last_success_revision" -e "tools/depends" ${DEPLOYED_BINARY_ADDONS} + +# if depends path has changed - cleanout everything and do a full rebuild +if [ "$(pathChanged $WORKSPACE/tools/depends)" == "1" ] +then + #clean up the rest too + cd $WORKSPACE;git clean -xffd + cd $WORKSPACE/tools/depends/;./bootstrap +fi diff --git a/tools/buildsteps/linux-aml/prepare-xbmc b/tools/buildsteps/linux-aml/prepare-xbmc new file mode 100644 index 00000000000..4ffc93bf6dc --- /dev/null +++ b/tools/buildsteps/linux-aml/prepare-xbmc @@ -0,0 +1,9 @@ +WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} +XBMC_PLATFORM_DIR=linux-aml +. $WORKSPACE/tools/buildsteps/defaultenv + +#build binary addons before building xbmc... +#make sure that binary_addons don't clean the native tools +#here (e.x. on release builds where pathChanged always returns 1 +BINARY_ADDONS_CLEAN_NATIVETOOLS="0" +. $WORKSPACE/tools/buildsteps/$XBMC_PLATFORM_DIR/make-binary-addons diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index a1563686ac9..96e331b5c58 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -447,7 +447,7 @@ case $host in platform_ldflags="-Wl,-rpath-link=$prefix/$deps_dir/lib" platform_os="linux" meson_system="linux" - target_platform="wayland gbm" + target_platform="wayland aml gbm" ;; *i686*-linux-gnu*|i*86*-*-linux-uclibc*|x86_64*-linux-gnu*|x86_64-*-linux-uclibc*) case $host in diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile index 296f6f5293d..60456f28be1 100644 --- a/tools/depends/target/Makefile +++ b/tools/depends/target/Makefile @@ -96,6 +96,9 @@ ifeq ($(OS),linux) ifeq ($(CPU),x86_64) DEPENDS += libva LIBVA = libva + else ifeq ($(TARGET_PLATFORM),aml) + DEPENDS += libxkbcommon libinput libudev libevdev mtdev + DEPENDS += libamcodec endif ifneq (,$(findstring wayland,$(TARGET_PLATFORM))) diff --git a/tools/depends/target/libamcodec/01-flags.patch b/tools/depends/target/libamcodec/01-flags.patch new file mode 100644 index 00000000000..d47b13eba1a --- /dev/null +++ b/tools/depends/target/libamcodec/01-flags.patch @@ -0,0 +1,22 @@ +--- a/amcodec/depends.mk ++++ b/amcodec/depends.mk +@@ -5,7 +5,7 @@ + include Makefile + + %.o.depend: +- @$(CC) -M -I$(CFLAGS) $*.c >$*.o.d ++ @$(CC) -M -I$(CFLAGS) $(EXT_CFLAGS) $*.c >$*.o.d + @echo ' $$(call c_mk,$$<)' >>$*.o.d + + +--- a/amcodec/rules.mk ++++ b/amcodec/rules.mk +@@ -21,7 +21,7 @@ + $(CC) \ + $(2:%-dir=%/build-in.o) \ + $(3:%-dir=%/build-in.o) \ +- $(LDFLAGS) -o $(1) ++ $(LDFLAGS) $(EXT_LDFLAGS) -o $(1) + + clr_mk=$(Q)echo "CLEAN $(1)";\ + $(MAKE) -C ${1} -f ${SRCTREE}/clean.mk diff --git a/tools/depends/target/libamcodec/02-no-amadec.patch b/tools/depends/target/libamcodec/02-no-amadec.patch new file mode 100644 index 00000000000..73420401435 --- /dev/null +++ b/tools/depends/target/libamcodec/02-no-amadec.patch @@ -0,0 +1,11 @@ +--- a/amcodec/Makefile ++++ b/amcodec/Makefile +@@ -29,7 +29,7 @@ + LIB_VERSION= + LIB_SUB_VERSION=.0.0 + +-LDFLAGS+= -O2 -L$(PREFIX)/lib -lamadec -lm -lc -shared -Wl,--shared -Wl,-soname,$(TARGET)$(LIB_VERSION) ++LDFLAGS+= -O2 -L$(PREFIX)/lib -lm -lc -shared -Wl,--shared -Wl,-soname,$(TARGET)$(LIB_VERSION) + + #INCLUDE=$(DIRS:%/=-I$(SRC)/%/include) + INCLUDE=${SRCTREE}/include/ diff --git a/tools/depends/target/libamcodec/Makefile b/tools/depends/target/libamcodec/Makefile new file mode 100644 index 00000000000..20ef180ae2f --- /dev/null +++ b/tools/depends/target/libamcodec/Makefile @@ -0,0 +1,38 @@ +include ../../Makefile.include +DEPS= ../../Makefile.include Makefile 01-flags.patch 02-no-amadec.patch + +# lib name, version +LIBNAME=libamcodec +VERSION=20170630 +SOURCE=$(LIBNAME)-$(VERSION) +ARCHIVE=$(SOURCE).tar.gz + +# configuration settings +CONFIGURE=./configure --prefix=$(PREFIX) --disable-shared + +LIBDYLIB=$(PLATFORM)/amcodec/libamcodec.so.0.0 + +all: .installed-$(PLATFORM) + +$(TARBALLS_LOCATION)/$(ARCHIVE): + cd $(TARBALLS_LOCATION); $(RETRIEVE_TOOL) $(RETRIEVE_TOOL_FLAGS) $(BASE_URL)/$(ARCHIVE) + +$(PLATFORM): $(TARBALLS_LOCATION)/$(ARCHIVE) $(DEPS) + rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) + cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + cd $(PLATFORM); patch -p1 -i ../01-flags.patch + cd $(PLATFORM); patch -p1 -i ../02-no-amadec.patch + +$(LIBDYLIB): $(PLATFORM) + $(MAKE) -C $(PLATFORM)/amcodec CC="$(CC)" EXT_CFLAGS="$(CFLAGS)" LD="$(LD)" EXT_LDFLAGS="$(LDFLAGS)" + +.installed-$(PLATFORM): $(LIBDYLIB) + $(MAKE) -C $(PLATFORM)/amcodec install CC="$(CC)" LD="$(LD)" PREFIX="$(PREFIX)" HEADERS_DIR="$(PREFIX)/include/amcodec" + touch $@ + +clean: + $(MAKE) -C $(PLATFORM) clean + rm -f .installed-$(PLATFORM) + +distclean:: + rm -rf $(PLATFORM) .installed-$(PLATFORM) diff --git a/xbmc/application/ApplicationSettingsHandling.cpp b/xbmc/application/ApplicationSettingsHandling.cpp index d2f17a076dc..ae33c46baaf 100644 --- a/xbmc/application/ApplicationSettingsHandling.cpp +++ b/xbmc/application/ApplicationSettingsHandling.cpp @@ -70,6 +70,7 @@ void CApplicationSettingsHandling::RegisterSettings() CSettings::SETTING_AUDIOCDS_SETTINGS, CSettings::SETTING_VIDEOSCREEN_GUICALIBRATION, CSettings::SETTING_VIDEOSCREEN_TESTPATTERN, + CSettings::SETTING_VIDEOPLAYER_USEAMCODEC, CSettings::SETTING_VIDEOPLAYER_USEMEDIACODEC, CSettings::SETTING_VIDEOPLAYER_USEMEDIACODECSURFACE, CSettings::SETTING_VIDEOPLAYER_USEDECODERFILTER, diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp index 0a7f162f2f5..c21d42bdeff 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp @@ -20,6 +20,10 @@ #include "utils/XTimeUtils.h" #include "utils/log.h" +#if defined(HAS_LIBAMCODEC) +#include "utils/AMLUtils.h" +#endif + #include #include #include @@ -519,6 +523,12 @@ bool CAESinkALSA::Initialize(AEAudioFormat &format, std::string &device) { m_passthrough = false; } +#if defined(HAS_LIBAMCODEC) + if (aml_present()) + { + aml_set_audio_passthrough(m_passthrough); + } +#endif if (inconfig.channels == 0) { diff --git a/xbmc/cores/RetroPlayer/process/amlogic/CMakeLists.txt b/xbmc/cores/RetroPlayer/process/amlogic/CMakeLists.txt new file mode 100644 index 00000000000..be5fcbdabe5 --- /dev/null +++ b/xbmc/cores/RetroPlayer/process/amlogic/CMakeLists.txt @@ -0,0 +1,6 @@ +if("aml" IN_LIST CORE_PLATFORM_NAME_LC) + set(SOURCES RPProcessInfoAmlogic.cpp) + set(HEADERS RPProcessInfoAmlogic.h) + + core_add_library(rp-process-amlogic) +endif() diff --git a/xbmc/cores/RetroPlayer/process/amlogic/RPProcessInfoAmlogic.cpp b/xbmc/cores/RetroPlayer/process/amlogic/RPProcessInfoAmlogic.cpp new file mode 100644 index 00000000000..f4ad256d671 --- /dev/null +++ b/xbmc/cores/RetroPlayer/process/amlogic/RPProcessInfoAmlogic.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RPProcessInfoAmlogic.h" +#include "utils/AMLUtils.h" +#include "utils/log.h" + +using namespace KODI; +using namespace RETRO; + +CRPProcessInfoAmlogic::CRPProcessInfoAmlogic() : + CRPProcessInfo("Amlogic") +{ +} + +CRPProcessInfo* CRPProcessInfoAmlogic::Create() +{ + return new CRPProcessInfoAmlogic(); +} + +void CRPProcessInfoAmlogic::Register() +{ + CRPProcessInfo::RegisterProcessControl(CRPProcessInfoAmlogic::Create); +} + +void CRPProcessInfoAmlogic::ConfigureRenderSystem(AVPixelFormat format) +{ + if (format == AV_PIX_FMT_0RGB32 || format == AV_PIX_FMT_0BGR32) + { + /* Set the Amlogic chip to ignore the alpha channel. + * The proprietary OpenGL lib does not (currently) + * handle this, potentially resulting in a black screen. + * This capability is only present in S905 chips and higher. + */ + if (aml_set_reg_ignore_alpha()) + CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Amlogic set to ignore alpha"); + } + else + { + if (aml_unset_reg_ignore_alpha()) + CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Amlogic unset to ignore alpha"); + } +} diff --git a/xbmc/cores/RetroPlayer/process/amlogic/RPProcessInfoAmlogic.h b/xbmc/cores/RetroPlayer/process/amlogic/RPProcessInfoAmlogic.h new file mode 100644 index 00000000000..13804026b92 --- /dev/null +++ b/xbmc/cores/RetroPlayer/process/amlogic/RPProcessInfoAmlogic.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "cores/RetroPlayer/process/RPProcessInfo.h" + +namespace KODI +{ +namespace RETRO +{ + class CRPProcessInfoAmlogic : public CRPProcessInfo + { + public: + CRPProcessInfoAmlogic(); + + static CRPProcessInfo* Create(); + static void Register(); + + // Implementation of CRPProcessInfo + void ConfigureRenderSystem(AVPixelFormat format) override; + + }; +} +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/AMLCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AMLCodec.cpp new file mode 100644 index 00000000000..0d1d02a07bf --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AMLCodec.cpp @@ -0,0 +1,2422 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + + +#include "AMLCodec.h" +#include "DynamicDll.h" + +#include "cores/VideoPlayer/Interface/TimingConstants.h" +#include "cores/VideoPlayer/Process/ProcessInfo.h" +#include "cores/VideoPlayer/VideoRenderers/RenderFlags.h" +#include "cores/VideoPlayer/VideoRenderers/RenderManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/DisplaySettings.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "utils/AMLUtils.h" +#include "utils/log.h" +#include "utils/StringUtils.h" +#include "utils/SysfsUtils.h" +#include "utils/TimeUtils.h" +#include "windowing/GraphicContext.h" +#include "windowing/WinSystem.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +std::mutex pollSyncMutex; + +} + +// amcodec include +extern "C" { +#include +} // extern "C" + +CEvent g_aml_sync_event; + +class PosixFile +{ +public: + PosixFile() : + m_fd(-1) + { + } + + PosixFile(int fd) : + m_fd(fd) + { + } + + ~PosixFile() + { + if (m_fd >= 0) + close(m_fd); + } + + bool Open(const std::string &pathName, int flags) + { + m_fd = open(pathName.c_str(), flags); + return m_fd >= 0; + } + + int GetDescriptor() const { return m_fd; } + + int IOControl(unsigned long request, void *param) + { + return ioctl(m_fd, request, param); + } + +private: + int m_fd; +}; + +typedef struct { + bool noblock; + int video_pid; + int video_type; + stream_type_t stream_type; + unsigned int format; + unsigned int width; + unsigned int height; + unsigned int rate; + unsigned int extra; + unsigned int status; + unsigned int ratio; + unsigned long long ratio64; + void *param; +} aml_generic_param; + +class DllLibamCodecInterface +{ +public: + virtual ~DllLibamCodecInterface() {}; + + virtual int codec_init(codec_para_t *pcodec)=0; + virtual int codec_close(codec_para_t *pcodec)=0; + virtual int codec_reset(codec_para_t *pcodec)=0; + virtual int codec_pause(codec_para_t *pcodec)=0; + virtual int codec_resume(codec_para_t *pcodec)=0; + virtual int codec_write(codec_para_t *pcodec, void *buffer, int len)=0; + virtual int codec_checkin_pts(codec_para_t *pcodec, unsigned long pts)=0; + virtual int codec_get_vbuf_state(codec_para_t *pcodec, struct buf_status *buf)=0; + virtual int codec_get_vdec_state(codec_para_t *pcodec, struct vdec_status *vdec)=0; + + virtual int codec_init_cntl(codec_para_t *pcodec)=0; + virtual int codec_poll_cntl(codec_para_t *pcodec)=0; + virtual int codec_set_cntl_mode(codec_para_t *pcodec, unsigned int mode)=0; + virtual int codec_set_cntl_avthresh(codec_para_t *pcodec, unsigned int avthresh)=0; + virtual int codec_set_cntl_syncthresh(codec_para_t *pcodec, unsigned int syncthresh)=0; + + virtual int codec_set_av_threshold(codec_para_t *pcodec, int threshold)=0; + virtual int codec_set_video_delay_limited_ms(codec_para_t *pcodec,int delay_ms)=0; + virtual int codec_get_video_delay_limited_ms(codec_para_t *pcodec,int *delay_ms)=0; + virtual int codec_get_video_cur_delay_ms(codec_para_t *pcodec,int *delay_ms)=0; +}; + +class DllLibAmCodec : public DllDynamic, DllLibamCodecInterface +{ + // libamcodec is static linked into libamcodec.so + DECLARE_DLL_WRAPPER(DllLibAmCodec, "libamcodec.so") + + DEFINE_METHOD1(int, codec_init, (codec_para_t *p1)) + DEFINE_METHOD1(int, codec_close, (codec_para_t *p1)) + DEFINE_METHOD1(int, codec_reset, (codec_para_t *p1)) + DEFINE_METHOD1(int, codec_pause, (codec_para_t *p1)) + DEFINE_METHOD1(int, codec_resume, (codec_para_t *p1)) + DEFINE_METHOD3(int, codec_write, (codec_para_t *p1, void *p2, int p3)) + DEFINE_METHOD2(int, codec_checkin_pts, (codec_para_t *p1, unsigned long p2)) + DEFINE_METHOD2(int, codec_get_vbuf_state, (codec_para_t *p1, struct buf_status * p2)) + DEFINE_METHOD2(int, codec_get_vdec_state, (codec_para_t *p1, struct vdec_status * p2)) + + DEFINE_METHOD1(int, codec_init_cntl, (codec_para_t *p1)) + DEFINE_METHOD1(int, codec_poll_cntl, (codec_para_t *p1)) + DEFINE_METHOD2(int, codec_set_cntl_mode, (codec_para_t *p1, unsigned int p2)) + DEFINE_METHOD2(int, codec_set_cntl_avthresh, (codec_para_t *p1, unsigned int p2)) + DEFINE_METHOD2(int, codec_set_cntl_syncthresh,(codec_para_t *p1, unsigned int p2)) + + DEFINE_METHOD2(int, codec_set_av_threshold, (codec_para_t *p1, int p2)) + DEFINE_METHOD2(int, codec_set_video_delay_limited_ms, (codec_para_t *p1, int p2)) + DEFINE_METHOD2(int, codec_get_video_delay_limited_ms, (codec_para_t *p1, int *p2)) + DEFINE_METHOD2(int, codec_get_video_cur_delay_ms, (codec_para_t *p1, int *p2)) + + BEGIN_METHOD_RESOLVE() + RESOLVE_METHOD(codec_init) + RESOLVE_METHOD(codec_close) + RESOLVE_METHOD(codec_reset) + RESOLVE_METHOD(codec_pause) + RESOLVE_METHOD(codec_resume) + RESOLVE_METHOD(codec_write) + RESOLVE_METHOD(codec_checkin_pts) + RESOLVE_METHOD(codec_get_vbuf_state) + RESOLVE_METHOD(codec_get_vdec_state) + + RESOLVE_METHOD(codec_init_cntl) + RESOLVE_METHOD(codec_poll_cntl) + RESOLVE_METHOD(codec_set_cntl_mode) + RESOLVE_METHOD(codec_set_cntl_avthresh) + RESOLVE_METHOD(codec_set_cntl_syncthresh) + + RESOLVE_METHOD(codec_set_av_threshold) + RESOLVE_METHOD(codec_set_video_delay_limited_ms) + RESOLVE_METHOD(codec_get_video_delay_limited_ms) + RESOLVE_METHOD(codec_get_video_cur_delay_ms) + END_METHOD_RESOLVE() + +public: + void codec_init_para(aml_generic_param *p_in, codec_para_t *p_out) + { + memset(p_out, 0x00, sizeof(codec_para_t)); + + // direct struct usage, we do not know which flavor + // so just use what we get from headers and pray. + p_out->handle = -1; //init to invalid + p_out->cntl_handle = -1; + p_out->sub_handle = -1; + p_out->audio_utils_handle = -1; + p_out->has_video = 1; + p_out->noblock = p_in->noblock; + p_out->video_pid = p_in->video_pid; + p_out->video_type = p_in->video_type; + p_out->stream_type = p_in->stream_type; + p_out->am_sysinfo.format = p_in->format; + p_out->am_sysinfo.width = p_in->width; + p_out->am_sysinfo.height = p_in->height; + p_out->am_sysinfo.rate = p_in->rate; + p_out->am_sysinfo.extra = p_in->extra; + p_out->am_sysinfo.status = p_in->status; + p_out->am_sysinfo.ratio = p_in->ratio; + p_out->am_sysinfo.ratio64 = p_in->ratio64; + p_out->am_sysinfo.param = p_in->param; + } +}; + +//----------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------- +// AppContext - Application state +#define MODE_3D_DISABLE 0x00000000 +#define MODE_3D_LR 0x00000101 +#define MODE_3D_LR_SWITCH 0x00000501 +#define MODE_3D_BT 0x00000201 +#define MODE_3D_BT_SWITCH 0x00000601 +#define MODE_3D_TO_2D_L 0x00000102 +#define MODE_3D_TO_2D_R 0x00000902 +#define MODE_3D_TO_2D_T 0x00000202 +#define MODE_3D_TO_2D_B 0x00000a02 + +#define PTS_FREQ 90000 +#define UNIT_FREQ 96000 +#define AV_SYNC_THRESH PTS_FREQ*30 + +#define TRICKMODE_NONE 0x00 +#define TRICKMODE_I 0x01 +#define TRICKMODE_FFFB 0x02 + +static const int64_t INT64_0 = 0x8000000000000000ULL; + +#define EXTERNAL_PTS (1) +#define SYNC_OUTSIDE (2) +#define KEYFRAME_PTS_ONLY 0x100 + +// missing tags +#ifndef CODEC_TAG_VC_1 +#define CODEC_TAG_VC_1 (0x312D4356) +#endif + +#ifndef HAS_LIBAMCODEC_VP9 +#define VFORMAT_VP9 VFORMAT_UNSUPPORT +#define VIDEO_DEC_FORMAT_VP9 VIDEO_DEC_FORMAT_MAX +#endif + +#define CODEC_TAG_RV30 (0x30335652) +#define CODEC_TAG_RV40 (0x30345652) +#define CODEC_TAG_MJPEG (0x47504a4d) +#define CODEC_TAG_mjpeg (0x47504a4c) +#define CODEC_TAG_jpeg (0x6765706a) +#define CODEC_TAG_mjpa (0x61706a6d) + +#define RW_WAIT_TIME (20 * 1000) // 20ms + +#define P_PRE (0x02000000) +#define F_PRE (0x03000000) +#define PLAYER_SUCCESS (0) +#define PLAYER_FAILED (-(P_PRE|0x01)) +#define PLAYER_NOMEM (-(P_PRE|0x02)) +#define PLAYER_EMPTY_P (-(P_PRE|0x03)) + +#define PLAYER_WR_FAILED (-(P_PRE|0x21)) +#define PLAYER_WR_EMPTYP (-(P_PRE|0x22)) +#define PLAYER_WR_FINISH (P_PRE|0x1) + +#define PLAYER_PTS_ERROR (-(P_PRE|0x31)) +#define PLAYER_UNSUPPORT (-(P_PRE|0x35)) +#define PLAYER_CHECK_CODEC_ERROR (-(P_PRE|0x39)) + +#define HDR_BUF_SIZE 1024 +typedef struct hdr_buf { + char *data; + int size; +} hdr_buf_t; + +typedef struct am_packet { + AVPacket avpkt; + int64_t avpts; + int64_t avdts; + int avduration; + int isvalid; + int newflag; + int64_t lastpts; + unsigned char *data; + unsigned char *buf; + int data_size; + int buf_size; + hdr_buf_t *hdr; + codec_para_t *codec; +} am_packet_t; + +typedef enum { + AM_STREAM_UNKNOWN = 0, + AM_STREAM_TS, + AM_STREAM_PS, + AM_STREAM_ES, + AM_STREAM_RM, + AM_STREAM_AUDIO, + AM_STREAM_VIDEO, +} pstream_type; + +typedef struct am_private_t +{ + am_packet_t am_pkt; + aml_generic_param gcodec; + codec_para_t vcodec; + + pstream_type stream_type; + int check_first_pts; + + vformat_t video_format; + int video_pid; + unsigned int video_codec_id; + unsigned int video_codec_tag; + vdec_type_t video_codec_type; + unsigned int video_width; + unsigned int video_height; + unsigned int video_ratio; + unsigned int video_ratio64; + unsigned int video_rate; + unsigned int video_rotation_degree; + FFmpegExtraData extradata; + DllLibAmCodec *m_dll; + + int dumpfile; + bool dumpdemux; +} am_private_t; + +typedef struct vframe_states +{ + int vf_pool_size; + int buf_free_num; + int buf_recycle_num; + int buf_avail_num; +} vframe_states_t; + +/*************************************************************************/ +/*************************************************************************/ +void dumpfile_open(am_private_t *para) +{ + if (para->dumpdemux) + { + static int amcodec_dumpid = 0; + char dump_path[128] = {0}; + sprintf(dump_path, "/temp/dump_amcodec-%d.dat", amcodec_dumpid++); + + para->dumpfile = open(dump_path, O_CREAT | O_RDWR, 0666); + } +} +void dumpfile_close(am_private_t *para) +{ + if (para->dumpdemux && para->dumpfile != -1) + close(para->dumpfile), para->dumpfile = -1; +} +void dumpfile_write(am_private_t *para, void* buf, int bufsiz) +{ + if (!buf) + { + CLog::Log(LOGERROR, "dumpfile_write: wtf ? buf is null, bufsiz({:d})", bufsiz); + return; + } + + if (para->dumpdemux && para->dumpfile != -1) + write(para->dumpfile, buf, bufsiz); +} + +static vformat_t codecid_to_vformat(enum AVCodecID id) +{ + vformat_t format; + switch (id) + { + case AV_CODEC_ID_MPEG1VIDEO: + case AV_CODEC_ID_MPEG2VIDEO: + format = VFORMAT_MPEG12; + break; + case AV_CODEC_ID_H263: + case AV_CODEC_ID_MPEG4: + case AV_CODEC_ID_H263P: + case AV_CODEC_ID_H263I: + case AV_CODEC_ID_MSMPEG4V2: + case AV_CODEC_ID_MSMPEG4V3: + case AV_CODEC_ID_FLV1: + format = VFORMAT_MPEG4; + break; + case AV_CODEC_ID_RV10: + case AV_CODEC_ID_RV20: + case AV_CODEC_ID_RV30: + case AV_CODEC_ID_RV40: + format = VFORMAT_REAL; + break; + case AV_CODEC_ID_H264: + format = VFORMAT_H264; + break; + /* + case AV_CODEC_ID_H264MVC: + // H264 Multiview Video Coding (3d blurays) + format = VFORMAT_H264MVC; + break; + */ + case AV_CODEC_ID_MJPEG: + format = VFORMAT_MJPEG; + break; + case AV_CODEC_ID_VC1: + case AV_CODEC_ID_WMV3: + format = VFORMAT_VC1; + break; + case AV_CODEC_ID_VP9: + format = VFORMAT_VP9; + break; + case AV_CODEC_ID_AVS: + case AV_CODEC_ID_CAVS: + format = VFORMAT_AVS; + break; + case AV_CODEC_ID_HEVC: + format = VFORMAT_HEVC; + break; + + default: + format = VFORMAT_UNSUPPORT; + break; + } + + CLog::Log(LOGDEBUG, "codecid_to_vformat, id({:d}) -> vformat({:d})", (int)id, format); + return format; +} + +static vdec_type_t codec_tag_to_vdec_type(unsigned int codec_tag) +{ + vdec_type_t dec_type; + switch (codec_tag) + { + case CODEC_TAG_MJPEG: + case CODEC_TAG_mjpeg: + case CODEC_TAG_jpeg: + case CODEC_TAG_mjpa: + // mjpeg + dec_type = VIDEO_DEC_FORMAT_MJPEG; + break; + case CODEC_TAG_XVID: + case CODEC_TAG_xvid: + case CODEC_TAG_XVIX: + // xvid + dec_type = VIDEO_DEC_FORMAT_MPEG4_5; + break; + case CODEC_TAG_COL1: + case CODEC_TAG_DIV3: + case CODEC_TAG_MP43: + // divx3.11 + dec_type = VIDEO_DEC_FORMAT_MPEG4_3; + break; + case CODEC_TAG_DIV4: + case CODEC_TAG_DIVX: + // divx4 + dec_type = VIDEO_DEC_FORMAT_MPEG4_4; + break; + case CODEC_TAG_DIV5: + case CODEC_TAG_DX50: + case CODEC_TAG_M4S2: + case CODEC_TAG_FMP4: + // divx5 + dec_type = VIDEO_DEC_FORMAT_MPEG4_5; + break; + case CODEC_TAG_DIV6: + // divx6 + dec_type = VIDEO_DEC_FORMAT_MPEG4_5; + break; + case CODEC_TAG_MP4V: + case CODEC_TAG_RMP4: + case CODEC_TAG_MPG4: + case CODEC_TAG_mp4v: + case AV_CODEC_ID_MPEG4: + // mp4 + dec_type = VIDEO_DEC_FORMAT_MPEG4_5; + break; + case AV_CODEC_ID_H263: + case CODEC_TAG_H263: + case CODEC_TAG_h263: + case CODEC_TAG_s263: + case CODEC_TAG_F263: + // h263 + dec_type = VIDEO_DEC_FORMAT_H263; + break; + case CODEC_TAG_AVC1: + case CODEC_TAG_avc1: + case CODEC_TAG_H264: + case CODEC_TAG_h264: + case AV_CODEC_ID_H264: + // h264 + dec_type = VIDEO_DEC_FORMAT_H264; + break; + /* + case AV_CODEC_ID_H264MVC: + dec_type = VIDEO_DEC_FORMAT_H264; + break; + */ + case AV_CODEC_ID_RV30: + case CODEC_TAG_RV30: + // realmedia 3 + dec_type = VIDEO_DEC_FORMAT_REAL_8; + break; + case AV_CODEC_ID_RV40: + case CODEC_TAG_RV40: + // realmedia 4 + dec_type = VIDEO_DEC_FORMAT_REAL_9; + break; + case CODEC_TAG_WMV3: + // wmv3 + dec_type = VIDEO_DEC_FORMAT_WMV3; + break; + case AV_CODEC_ID_VC1: + case CODEC_TAG_VC_1: + case CODEC_TAG_WVC1: + case CODEC_TAG_WMVA: + // vc1 + dec_type = VIDEO_DEC_FORMAT_WVC1; + break; + case AV_CODEC_ID_VP6F: + // vp6 + dec_type = VIDEO_DEC_FORMAT_SW; + break; + case AV_CODEC_ID_VP9: + dec_type = VIDEO_DEC_FORMAT_VP9; + break; + case AV_CODEC_ID_CAVS: + case AV_CODEC_ID_AVS: + // avs + dec_type = VIDEO_DEC_FORMAT_AVS; + break; + case AV_CODEC_ID_HEVC: + // h265 + dec_type = VIDEO_DEC_FORMAT_HEVC; + break; + default: + dec_type = VIDEO_DEC_FORMAT_UNKNOW; + break; + } + + CLog::Log(LOGDEBUG, "codec_tag_to_vdec_type, codec_tag({:d}) -> vdec_type({:d})", codec_tag, dec_type); + return dec_type; +} + +static void am_packet_init(am_packet_t *pkt) +{ + memset(&pkt->avpkt, 0, sizeof(AVPacket)); + pkt->avpts = 0; + pkt->avdts = 0; + pkt->avduration = 0; + pkt->isvalid = 0; + pkt->newflag = 1; + pkt->lastpts = INT64_0; + pkt->data = NULL; + pkt->buf = NULL; + pkt->data_size = 0; + pkt->buf_size = 0; + pkt->hdr = NULL; + pkt->codec = NULL; +} + +void am_packet_release(am_packet_t *pkt) +{ + if (pkt->buf != NULL) + free(pkt->buf), pkt->buf= NULL; + if (pkt->hdr != NULL) + { + if (pkt->hdr->data != NULL) + free(pkt->hdr->data), pkt->hdr->data = NULL; + free(pkt->hdr), pkt->hdr = NULL; + } + + pkt->codec = NULL; +} + +int check_in_pts(am_private_t *para, am_packet_t *pkt) +{ + if (para->stream_type == AM_STREAM_ES + && INT64_0 != pkt->avpts + && para->m_dll->codec_checkin_pts(pkt->codec, pkt->avpts) != 0) + { + CLog::Log(LOGDEBUG, "ERROR check in pts error!"); + return PLAYER_PTS_ERROR; + } + return PLAYER_SUCCESS; +} + +static int write_header(am_private_t *para, am_packet_t *pkt) +{ + int write_bytes = 0, len = 0; + + if (pkt->hdr && pkt->hdr->size > 0) { + if ((NULL == pkt->codec) || (NULL == pkt->hdr->data)) { + CLog::Log(LOGDEBUG, "[write_header]codec null!"); + return PLAYER_EMPTY_P; + } + //some wvc1 es data not need to add header + if (para->video_format == VFORMAT_VC1 && para->video_codec_type == VIDEO_DEC_FORMAT_WVC1) { + if ((pkt->data) && (pkt->data_size >= 4) + && (pkt->data[0] == 0) && (pkt->data[1] == 0) + && (pkt->data[2] == 1) && (pkt->data[3] == 0xd || pkt->data[3] == 0xf)) { + return PLAYER_SUCCESS; + } + } + while (1) { + write_bytes = para->m_dll->codec_write(pkt->codec, pkt->hdr->data + len, pkt->hdr->size - len); + if (write_bytes < 0 || write_bytes > (pkt->hdr->size - len)) { + if (-errno != AVERROR(EAGAIN)) { + CLog::Log(LOGDEBUG, "ERROR:write header failed!"); + return PLAYER_WR_FAILED; + } else { + continue; + } + } else { + dumpfile_write(para, pkt->hdr->data, write_bytes); + len += write_bytes; + if (len == pkt->hdr->size) { + break; + } + } + } + } + return PLAYER_SUCCESS; +} + +int check_avbuffer_enough(am_private_t *para, am_packet_t *pkt) +{ + return 1; +} + +int write_av_packet(am_private_t *para, am_packet_t *pkt) +{ + //CLog::Log(LOGDEBUG, "write_av_packet, pkt->isvalid({:d}), pkt->data({:p}), pkt->data_size({:d})", + // pkt->isvalid, pkt->data, pkt->data_size); + + int write_bytes = 0, len = 0, ret; + unsigned char *buf; + int size; + + // do we need to check in pts or write the header ? + if (pkt->newflag) { + if (pkt->isvalid) { + ret = check_in_pts(para, pkt); + if (ret != PLAYER_SUCCESS) { + CLog::Log(LOGDEBUG, "check in pts failed"); + return PLAYER_WR_FAILED; + } + } + if (write_header(para, pkt) == PLAYER_WR_FAILED) { + CLog::Log(LOGDEBUG, "[{}]write header failed!", __FUNCTION__); + return PLAYER_WR_FAILED; + } + if (pkt->data_size) + pkt->newflag = 0; + } + + buf = pkt->data; + size = pkt->data_size ; + if (size == 0 && pkt->isvalid) { + pkt->isvalid = 0; + pkt->data_size = 0; + } + + while (size > 0 && pkt->isvalid) { + write_bytes = para->m_dll->codec_write(pkt->codec, buf, size); + if (write_bytes < 0 || write_bytes > size) { + CLog::Log(LOGDEBUG, "write codec data failed, write_bytes({:d}), errno({:d}), size({:d})", write_bytes, errno, size); + if (-errno != AVERROR(EAGAIN)) { + CLog::Log(LOGDEBUG, "write codec data failed!"); + return PLAYER_WR_FAILED; + } else { + // adjust for any data we already wrote into codec. + // we sleep a bit then exit as we will get called again + // with the same pkt because pkt->isvalid has not been cleared. + pkt->data += len; + pkt->data_size -= len; + usleep(RW_WAIT_TIME); + CLog::Log(LOGDEBUG, "usleep(RW_WAIT_TIME), len({:d})", len); + return PLAYER_SUCCESS; + } + } else { + dumpfile_write(para, buf, write_bytes); + // keep track of what we write into codec from this pkt + // in case we get hit with EAGAIN. + len += write_bytes; + if (len == pkt->data_size) { + pkt->isvalid = 0; + pkt->data_size = 0; + break; + } else if (len < pkt->data_size) { + buf += write_bytes; + size -= write_bytes; + } else { + // writing more that we should is a failure. + return PLAYER_WR_FAILED; + } + } + } + + return PLAYER_SUCCESS; +} + +/*************************************************************************/ +static int m4s2_dx50_mp4v_add_header(unsigned char *buf, int size, am_packet_t *pkt) +{ + if (size > pkt->hdr->size) { + free(pkt->hdr->data), pkt->hdr->data = NULL; + pkt->hdr->size = 0; + + pkt->hdr->data = (char*)malloc(size); + if (!pkt->hdr->data) { + CLog::Log(LOGDEBUG, "[m4s2_dx50_add_header] NOMEM!"); + return PLAYER_FAILED; + } + } + + pkt->hdr->size = size; + memcpy(pkt->hdr->data, buf, size); + + return PLAYER_SUCCESS; +} + +static int m4s2_dx50_mp4v_write_header(am_private_t *para, am_packet_t *pkt) +{ + CLog::Log(LOGDEBUG, "m4s2_dx50_mp4v_write_header"); + int ret = m4s2_dx50_mp4v_add_header(para->extradata.GetData(), para->extradata.GetSize(), pkt); + if (ret == PLAYER_SUCCESS) { + if (1) { + pkt->codec = ¶->vcodec; + } else { + CLog::Log(LOGDEBUG, "[m4s2_dx50_mp4v_add_header]invalid video codec!"); + return PLAYER_EMPTY_P; + } + pkt->newflag = 1; + ret = write_av_packet(para, pkt); + } + return ret; +} + +static int mjpeg_data_prefeeding(am_packet_t *pkt) +{ + const unsigned char mjpeg_addon_data[] = { + 0xff, 0xd8, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x03, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, + 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, + 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, + 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, + 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, + 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, + 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x11, 0x00, 0x02, 0x01, + 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, + 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, + 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, + 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }; + + if (pkt->hdr->data) { + memcpy(pkt->hdr->data, &mjpeg_addon_data, sizeof(mjpeg_addon_data)); + pkt->hdr->size = sizeof(mjpeg_addon_data); + } else { + CLog::Log(LOGDEBUG, "[mjpeg_data_prefeeding]No enough memory!"); + return PLAYER_FAILED; + } + return PLAYER_SUCCESS; +} + +static int mjpeg_write_header(am_private_t *para, am_packet_t *pkt) +{ + mjpeg_data_prefeeding(pkt); + if (1) { + pkt->codec = ¶->vcodec; + } else { + CLog::Log(LOGDEBUG, "[mjpeg_write_header]invalid codec!"); + return PLAYER_EMPTY_P; + } + pkt->newflag = 1; + write_av_packet(para, pkt); + return PLAYER_SUCCESS; +} + +static int divx3_data_prefeeding(am_packet_t *pkt, unsigned w, unsigned h) +{ + unsigned i = (w << 12) | (h & 0xfff); + unsigned char divx311_add[10] = { + 0x00, 0x00, 0x00, 0x01, + 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + divx311_add[5] = (i >> 16) & 0xff; + divx311_add[6] = (i >> 8) & 0xff; + divx311_add[7] = i & 0xff; + + if (pkt->hdr->data) { + memcpy(pkt->hdr->data, divx311_add, sizeof(divx311_add)); + pkt->hdr->size = sizeof(divx311_add); + } else { + CLog::Log(LOGDEBUG, "[divx3_data_prefeeding]No enough memory!"); + return PLAYER_FAILED; + } + return PLAYER_SUCCESS; +} + +static int divx3_write_header(am_private_t *para, am_packet_t *pkt) +{ + CLog::Log(LOGDEBUG, "divx3_write_header"); + divx3_data_prefeeding(pkt, para->video_width, para->video_height); + if (1) { + pkt->codec = ¶->vcodec; + } else { + CLog::Log(LOGDEBUG, "[divx3_write_header]invalid codec!"); + return PLAYER_EMPTY_P; + } + pkt->newflag = 1; + write_av_packet(para, pkt); + return PLAYER_SUCCESS; +} + +static int h264_add_header(unsigned char *buf, int size, am_packet_t *pkt) +{ + if (size > HDR_BUF_SIZE) + { + free(pkt->hdr->data); + pkt->hdr->data = (char *)malloc(size); + if (!pkt->hdr->data) + return PLAYER_NOMEM; + } + + memcpy(pkt->hdr->data, buf, size); + pkt->hdr->size = size; + return PLAYER_SUCCESS; +} + +static int h264_write_header(am_private_t *para, am_packet_t *pkt) +{ + // CLog::Log(LOGDEBUG, "h264_write_header"); + int ret = h264_add_header(para->extradata.GetData(), para->extradata.GetSize(), pkt); + if (ret == PLAYER_SUCCESS) { + //if (ctx->vcodec) { + if (1) { + pkt->codec = ¶->vcodec; + } else { + //CLog::Log(LOGDEBUG, "[pre_header_feeding]invalid video codec!"); + return PLAYER_EMPTY_P; + } + + pkt->newflag = 1; + ret = write_av_packet(para, pkt); + } + return ret; +} + +static int hevc_add_header(unsigned char *buf, int size, am_packet_t *pkt) +{ + if (size > HDR_BUF_SIZE) + { + free(pkt->hdr->data); + pkt->hdr->data = (char *)malloc(size); + if (!pkt->hdr->data) + return PLAYER_NOMEM; + } + + memcpy(pkt->hdr->data, buf, size); + pkt->hdr->size = size; + return PLAYER_SUCCESS; +} + +static int hevc_write_header(am_private_t *para, am_packet_t *pkt) +{ + int ret = -1; + + if (para->extradata) { + ret = hevc_add_header(para->extradata.GetData(), para->extradata.GetSize(), pkt); + } else { + if (pkt->hdr) { + if (pkt->hdr->data) { + free(pkt->hdr->data); + pkt->hdr->data = NULL; + } + free(pkt->hdr); + pkt->hdr = NULL; + } + } + + if (ret == PLAYER_SUCCESS) { + pkt->codec = ¶->vcodec; + pkt->newflag = 1; + ret = write_av_packet(para, pkt); + } + + return ret; +} + +int vp9_update_frame_header(am_packet_t *pkt) +{ + int dsize = pkt->data_size; + unsigned char *buf = pkt->data; + unsigned char marker; + int frame_number; + int cur_frame, cur_mag, mag, index_sz, offset[9], size[8], tframesize[9]; + int mag_ptr; + int ret; + unsigned char *old_header = NULL; + int total_datasize = 0; + + pkt->avpkt.data = pkt->data; + pkt->avpkt.size = pkt->data_size; + + if (buf == NULL) + return PLAYER_SUCCESS; /*something error. skip add header*/ + + marker = buf[dsize - 1]; + + if ((marker & 0xe0) == 0xc0) + { + frame_number = (marker & 0x7) + 1; + mag = ((marker >> 3) & 0x3) + 1; + index_sz = 2 + mag * frame_number; + CLog::Log(LOGDEBUG, " frame_number : {:d}, mag : {:d}; index_sz : {:d}\n", frame_number, mag, index_sz); + offset[0] = 0; + mag_ptr = dsize - mag * frame_number - 2; + if (buf[mag_ptr] != marker) + { + CLog::Log(LOGDEBUG, " Wrong marker2 : 0x{:X} --> 0x{:X}\n", marker, buf[mag_ptr]); + return PLAYER_SUCCESS; + } + + mag_ptr++; + + for (cur_frame = 0; cur_frame < frame_number; cur_frame++) + { + size[cur_frame] = 0; // or size[0] = bytes_in_buffer - 1; both OK + + for (cur_mag = 0; cur_mag < mag; cur_mag++) + { + size[cur_frame] = size[cur_frame]|(buf[mag_ptr] << (cur_mag*8)); + mag_ptr++; + } + + offset[cur_frame+1] = offset[cur_frame] + size[cur_frame]; + + if (cur_frame == 0) + tframesize[cur_frame] = size[cur_frame]; + else + tframesize[cur_frame] = tframesize[cur_frame - 1] + size[cur_frame]; + + total_datasize += size[cur_frame]; + } + } + else + { + frame_number = 1; + offset[0] = 0; + size[0] = dsize; // or size[0] = bytes_in_buffer - 1; both OK + total_datasize += dsize; + tframesize[0] = dsize; + } + + if (total_datasize > dsize) + { + CLog::Log(LOGDEBUG, "DATA overflow : 0x{:X} --> 0x{:X}\n", total_datasize, dsize); + return PLAYER_SUCCESS; + } + + if (frame_number >= 1) + { + /* + if only one frame ,can used headers. + */ + int need_more = total_datasize + frame_number * 16 - dsize; + + av_buffer_unref(&pkt->avpkt.buf); + ret = av_grow_packet(&(pkt->avpkt), need_more); + if (ret < 0) + { + CLog::Log(LOGDEBUG, "ERROR!!! grow_packet for apk failed.!!!\n"); + return ret; + } + + pkt->data = pkt->avpkt.data; + pkt->data_size = pkt->avpkt.size; + } + + for (cur_frame = frame_number - 1; cur_frame >= 0; cur_frame--) + { + AVPacket *avpkt = &(pkt->avpkt); + int framesize = size[cur_frame]; + int oldframeoff = tframesize[cur_frame] - framesize; + int outheaderoff = oldframeoff + cur_frame * 16; + uint8_t *fdata = avpkt->data + outheaderoff; + uint8_t *old_framedata = avpkt->data + oldframeoff; + memmove(fdata + 16, old_framedata, framesize); + framesize += 4;/*add 4. for shift.....*/ + + /*add amlogic frame headers.*/ + fdata[0] = (framesize >> 24) & 0xff; + fdata[1] = (framesize >> 16) & 0xff; + fdata[2] = (framesize >> 8) & 0xff; + fdata[3] = (framesize >> 0) & 0xff; + fdata[4] = ((framesize >> 24) & 0xff) ^0xff; + fdata[5] = ((framesize >> 16) & 0xff) ^0xff; + fdata[6] = ((framesize >> 8) & 0xff) ^0xff; + fdata[7] = ((framesize >> 0) & 0xff) ^0xff; + fdata[8] = 0; + fdata[9] = 0; + fdata[10] = 0; + fdata[11] = 1; + fdata[12] = 'A'; + fdata[13] = 'M'; + fdata[14] = 'L'; + fdata[15] = 'V'; + framesize -= 4;/*del 4 to real framesize for check.....*/ + + if (!old_header) + { + // nothing + } + else if (old_header > fdata + 16 + framesize) + { + CLog::Log(LOGDEBUG, "data has gaps,set to 0\n"); + memset(fdata + 16 + framesize, 0, (old_header - fdata + 16 + framesize)); + } + else if (old_header < fdata + 16 + framesize) + CLog::Log(LOGDEBUG, "ERROR!!! data over writed!!!! over write {:d}\n", fdata + 16 + framesize - old_header); + + old_header = fdata; + } + + return PLAYER_SUCCESS; +} + +static int wmv3_write_header(am_private_t *para, am_packet_t *pkt) +{ + CLog::Log(LOGDEBUG, "wmv3_write_header"); + unsigned i, check_sum = 0; + unsigned data_len = para->extradata.GetSize() + 4; + + pkt->hdr->data[0] = 0; + pkt->hdr->data[1] = 0; + pkt->hdr->data[2] = 1; + pkt->hdr->data[3] = 0x10; + + pkt->hdr->data[4] = 0; + pkt->hdr->data[5] = (data_len >> 16) & 0xff; + pkt->hdr->data[6] = 0x88; + pkt->hdr->data[7] = (data_len >> 8) & 0xff; + pkt->hdr->data[8] = data_len & 0xff; + pkt->hdr->data[9] = 0x88; + + pkt->hdr->data[10] = 0xff; + pkt->hdr->data[11] = 0xff; + pkt->hdr->data[12] = 0x88; + pkt->hdr->data[13] = 0xff; + pkt->hdr->data[14] = 0xff; + pkt->hdr->data[15] = 0x88; + + for (i = 4 ; i < 16 ; i++) { + check_sum += pkt->hdr->data[i]; + } + + pkt->hdr->data[16] = (check_sum >> 8) & 0xff; + pkt->hdr->data[17] = check_sum & 0xff; + pkt->hdr->data[18] = 0x88; + pkt->hdr->data[19] = (check_sum >> 8) & 0xff; + pkt->hdr->data[20] = check_sum & 0xff; + pkt->hdr->data[21] = 0x88; + + pkt->hdr->data[22] = (para->video_width >> 8) & 0xff; + pkt->hdr->data[23] = para->video_width & 0xff; + pkt->hdr->data[24] = (para->video_height >> 8) & 0xff; + pkt->hdr->data[25] = para->video_height & 0xff; + + memcpy(pkt->hdr->data + 26, para->extradata.GetData(), para->extradata.GetSize()); + pkt->hdr->size = para->extradata.GetSize() + 26; + if (1) { + pkt->codec = ¶->vcodec; + } else { + CLog::Log(LOGDEBUG, "[wmv3_write_header]invalid codec!"); + return PLAYER_EMPTY_P; + } + pkt->newflag = 1; + return write_av_packet(para, pkt); +} + +static int wvc1_write_header(am_private_t *para, am_packet_t *pkt) +{ + CLog::Log(LOGDEBUG, "wvc1_write_header"); + memcpy(pkt->hdr->data, para->extradata.GetData() + 1, para->extradata.GetSize() - 1); + pkt->hdr->size = para->extradata.GetSize() - 1; + if (1) { + pkt->codec = ¶->vcodec; + } else { + CLog::Log(LOGDEBUG, "[wvc1_write_header]invalid codec!"); + return PLAYER_EMPTY_P; + } + pkt->newflag = 1; + return write_av_packet(para, pkt); +} + +static int mpeg_add_header(am_private_t *para, am_packet_t *pkt) +{ + CLog::Log(LOGDEBUG, "mpeg_add_header"); +#define STUFF_BYTES_LENGTH (256) + int size; + unsigned char packet_wrapper[] = { + 0x00, 0x00, 0x01, 0xe0, + 0x00, 0x00, /* pes packet length */ + 0x81, 0xc0, 0x0d, + 0x20, 0x00, 0x00, 0x00, 0x00, /* PTS */ + 0x1f, 0xff, 0xff, 0xff, 0xff, /* DTS */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + size = para->extradata.GetSize() + sizeof(packet_wrapper); + packet_wrapper[4] = size >> 8 ; + packet_wrapper[5] = size & 0xff ; + memcpy(pkt->hdr->data, packet_wrapper, sizeof(packet_wrapper)); + size = sizeof(packet_wrapper); + //CLog::Log(LOGDEBUG, "[mpeg_add_header:{:d}]wrapper size={:d}\n",__LINE__,size); + memcpy(pkt->hdr->data + size, para->extradata.GetData(), para->extradata.GetSize()); + size += para->extradata.GetSize(); + //CLog::Log(LOGDEBUG, "[mpeg_add_header:{:d}]wrapper+seq size={:d}\n",__LINE__,size); + memset(pkt->hdr->data + size, 0xff, STUFF_BYTES_LENGTH); + size += STUFF_BYTES_LENGTH; + pkt->hdr->size = size; + //CLog::Log(LOGDEBUG, "[mpeg_add_header:{:d}]hdr_size={:d}\n",__LINE__,size); + if (1) { + pkt->codec = ¶->vcodec; + } else { + CLog::Log(LOGDEBUG, "[mpeg_add_header]invalid codec!"); + return PLAYER_EMPTY_P; + } + + pkt->newflag = 1; + return write_av_packet(para, pkt); +} + +int pre_header_feeding(am_private_t *para, am_packet_t *pkt) +{ + int ret; + if (para->stream_type == AM_STREAM_ES) { + if (pkt->hdr == NULL) { + pkt->hdr = (hdr_buf_t*)malloc(sizeof(hdr_buf_t)); + pkt->hdr->data = (char *)malloc(HDR_BUF_SIZE); + if (!pkt->hdr->data) { + //CLog::Log(LOGDEBUG, "[pre_header_feeding] NOMEM!"); + return PLAYER_NOMEM; + } + } + + if (VFORMAT_H264 == para->video_format || VFORMAT_H264_4K2K == para->video_format) { + ret = h264_write_header(para, pkt); + if (ret != PLAYER_SUCCESS) { + return ret; + } + } else if ((VFORMAT_MPEG4 == para->video_format) && (VIDEO_DEC_FORMAT_MPEG4_3 == para->video_codec_type)) { + ret = divx3_write_header(para, pkt); + if (ret != PLAYER_SUCCESS) { + return ret; + } + } else if ((CODEC_TAG_M4S2 == para->video_codec_tag) + || (CODEC_TAG_DX50 == para->video_codec_tag) + || (CODEC_TAG_mp4v == para->video_codec_tag)) { + ret = m4s2_dx50_mp4v_write_header(para, pkt); + if (ret != PLAYER_SUCCESS) { + return ret; + } + /* + } else if ((AVI_FILE == para->file_type) + && (VIDEO_DEC_FORMAT_MPEG4_3 != para->vstream_info.video_codec_type) + && (VFORMAT_H264 != para->vstream_info.video_format) + && (VFORMAT_VC1 != para->vstream_info.video_format)) { + ret = avi_write_header(para); + if (ret != PLAYER_SUCCESS) { + return ret; + } + */ + } else if (CODEC_TAG_WMV3 == para->video_codec_tag) { + CLog::Log(LOGDEBUG, "CODEC_TAG_WMV3 == para->video_codec_tag"); + ret = wmv3_write_header(para, pkt); + if (ret != PLAYER_SUCCESS) { + return ret; + } + } else if ((CODEC_TAG_WVC1 == para->video_codec_tag) + || (CODEC_TAG_VC_1 == para->video_codec_tag) + || (CODEC_TAG_WMVA == para->video_codec_tag)) { + if (para->extradata.GetSize() > 4 && !para->extradata.GetData()[0] && !para->extradata.GetData()[1] && + para->extradata.GetData()[2] == 0x01 && para->extradata.GetData()[3] == 0x0f && ((para->extradata.GetData()[4] & 0x03) == 0x03)) + { + CLog::Log(LOGDEBUG, "CODEC_TAG_WVC1 == para->video_codec_tag, using wmv3_write_header"); + ret = wmv3_write_header(para, pkt); + } + else + { + CLog::Log(LOGDEBUG, "CODEC_TAG_WVC1 == para->video_codec_tag"); + ret = wvc1_write_header(para, pkt); + } + if (ret != PLAYER_SUCCESS) { + return ret; + } + /* + } else if ((MKV_FILE == para->file_type) && + ((VFORMAT_MPEG4 == para->vstream_info.video_format) + || (VFORMAT_MPEG12 == para->vstream_info.video_format))) { + ret = mkv_write_header(para, pkt); + if (ret != PLAYER_SUCCESS) { + return ret; + } + */ + } else if (VFORMAT_MJPEG == para->video_format) { + ret = mjpeg_write_header(para, pkt); + if (ret != PLAYER_SUCCESS) { + return ret; + } + } else if (VFORMAT_HEVC == para->video_format) { + ret = hevc_write_header(para, pkt); + if (ret != PLAYER_SUCCESS) { + return ret; + } + } + + if (pkt->hdr) { + if (pkt->hdr->data) { + free(pkt->hdr->data); + pkt->hdr->data = NULL; + } + free(pkt->hdr); + pkt->hdr = NULL; + } + } + else if (para->stream_type == AM_STREAM_PS) { + if (pkt->hdr == NULL) { + pkt->hdr = (hdr_buf_t*)malloc(sizeof(hdr_buf_t)); + pkt->hdr->data = (char*)malloc(HDR_BUF_SIZE); + if (!pkt->hdr->data) { + CLog::Log(LOGDEBUG, "[pre_header_feeding] NOMEM!"); + return PLAYER_NOMEM; + } + } + if (( AV_CODEC_ID_MPEG1VIDEO == para->video_codec_id) + || (AV_CODEC_ID_MPEG2VIDEO == para->video_codec_id)) { + ret = mpeg_add_header(para, pkt); + if (ret != PLAYER_SUCCESS) { + return ret; + } + } + if (pkt->hdr) { + if (pkt->hdr->data) { + free(pkt->hdr->data); + pkt->hdr->data = NULL; + } + free(pkt->hdr); + pkt->hdr = NULL; + } + } + return PLAYER_SUCCESS; +} + +int divx3_prefix(am_packet_t *pkt) +{ +#define DIVX311_CHUNK_HEAD_SIZE 13 + const unsigned char divx311_chunk_prefix[DIVX311_CHUNK_HEAD_SIZE] = { + 0x00, 0x00, 0x00, 0x01, 0xb6, 'D', 'I', 'V', 'X', '3', '.', '1', '1' + }; + if ((pkt->hdr != NULL) && (pkt->hdr->data != NULL)) { + free(pkt->hdr->data); + pkt->hdr->data = NULL; + } + + if (pkt->hdr == NULL) { + pkt->hdr = (hdr_buf_t*)malloc(sizeof(hdr_buf_t)); + if (!pkt->hdr) { + CLog::Log(LOGDEBUG, "[divx3_prefix] NOMEM!"); + return PLAYER_FAILED; + } + + pkt->hdr->data = NULL; + pkt->hdr->size = 0; + } + + pkt->hdr->data = (char*)malloc(DIVX311_CHUNK_HEAD_SIZE + 4); + if (pkt->hdr->data == NULL) { + CLog::Log(LOGDEBUG, "[divx3_prefix] NOMEM!"); + return PLAYER_FAILED; + } + + memcpy(pkt->hdr->data, divx311_chunk_prefix, DIVX311_CHUNK_HEAD_SIZE); + + pkt->hdr->data[DIVX311_CHUNK_HEAD_SIZE + 0] = (pkt->data_size >> 24) & 0xff; + pkt->hdr->data[DIVX311_CHUNK_HEAD_SIZE + 1] = (pkt->data_size >> 16) & 0xff; + pkt->hdr->data[DIVX311_CHUNK_HEAD_SIZE + 2] = (pkt->data_size >> 8) & 0xff; + pkt->hdr->data[DIVX311_CHUNK_HEAD_SIZE + 3] = pkt->data_size & 0xff; + + pkt->hdr->size = DIVX311_CHUNK_HEAD_SIZE + 4; + pkt->newflag = 1; + + return PLAYER_SUCCESS; +} + +int set_header_info(am_private_t *para) +{ + am_packet_t *pkt = ¶->am_pkt; + + //if (pkt->newflag) + { + //if (pkt->hdr) + // pkt->hdr->size = 0; + + if (para->video_format == VFORMAT_MPEG4) + { + if (para->video_codec_type == VIDEO_DEC_FORMAT_MPEG4_3) + { + return divx3_prefix(pkt); + } + else if (para->video_codec_type == VIDEO_DEC_FORMAT_H263) + { + return PLAYER_UNSUPPORT; + } + } else if (para->video_format == VFORMAT_VC1) { + if (para->video_codec_type == VIDEO_DEC_FORMAT_WMV3) { + unsigned i, check_sum = 0, data_len = 0; + + if ((pkt->hdr != NULL) && (pkt->hdr->data != NULL)) { + free(pkt->hdr->data); + pkt->hdr->data = NULL; + } + + if (pkt->hdr == NULL) { + pkt->hdr = (hdr_buf_t*)malloc(sizeof(hdr_buf_t)); + if (!pkt->hdr) { + return PLAYER_FAILED; + } + + pkt->hdr->data = NULL; + pkt->hdr->size = 0; + } + + if (pkt->avpkt.flags) { + pkt->hdr->data = (char*)malloc(para->extradata.GetSize() + 26 + 22); + if (pkt->hdr->data == NULL) { + return PLAYER_FAILED; + } + + pkt->hdr->data[0] = 0; + pkt->hdr->data[1] = 0; + pkt->hdr->data[2] = 1; + pkt->hdr->data[3] = 0x10; + + data_len = para->extradata.GetSize() + 4; + pkt->hdr->data[4] = 0; + pkt->hdr->data[5] = (data_len >> 16) & 0xff; + pkt->hdr->data[6] = 0x88; + pkt->hdr->data[7] = (data_len >> 8) & 0xff; + pkt->hdr->data[8] = data_len & 0xff; + pkt->hdr->data[9] = 0x88; + + pkt->hdr->data[10] = 0xff; + pkt->hdr->data[11] = 0xff; + pkt->hdr->data[12] = 0x88; + pkt->hdr->data[13] = 0xff; + pkt->hdr->data[14] = 0xff; + pkt->hdr->data[15] = 0x88; + + for (i = 4 ; i < 16 ; i++) { + check_sum += pkt->hdr->data[i]; + } + + pkt->hdr->data[16] = (check_sum >> 8) & 0xff; + pkt->hdr->data[17] = check_sum & 0xff; + pkt->hdr->data[18] = 0x88; + pkt->hdr->data[19] = (check_sum >> 8) & 0xff; + pkt->hdr->data[20] = check_sum & 0xff; + pkt->hdr->data[21] = 0x88; + + pkt->hdr->data[22] = (para->video_width >> 8) & 0xff; + pkt->hdr->data[23] = para->video_width & 0xff; + pkt->hdr->data[24] = (para->video_height >> 8) & 0xff; + pkt->hdr->data[25] = para->video_height & 0xff; + + memcpy(pkt->hdr->data + 26, para->extradata.GetData(), para->extradata.GetSize()); + + check_sum = 0; + data_len = para->extradata.GetSize() + 26; + } else { + pkt->hdr->data = (char*)malloc(22); + if (pkt->hdr->data == NULL) { + return PLAYER_FAILED; + } + } + + pkt->hdr->data[data_len + 0] = 0; + pkt->hdr->data[data_len + 1] = 0; + pkt->hdr->data[data_len + 2] = 1; + pkt->hdr->data[data_len + 3] = 0xd; + + pkt->hdr->data[data_len + 4] = 0; + pkt->hdr->data[data_len + 5] = (pkt->data_size >> 16) & 0xff; + pkt->hdr->data[data_len + 6] = 0x88; + pkt->hdr->data[data_len + 7] = (pkt->data_size >> 8) & 0xff; + pkt->hdr->data[data_len + 8] = pkt->data_size & 0xff; + pkt->hdr->data[data_len + 9] = 0x88; + + pkt->hdr->data[data_len + 10] = 0xff; + pkt->hdr->data[data_len + 11] = 0xff; + pkt->hdr->data[data_len + 12] = 0x88; + pkt->hdr->data[data_len + 13] = 0xff; + pkt->hdr->data[data_len + 14] = 0xff; + pkt->hdr->data[data_len + 15] = 0x88; + + for (i = data_len + 4 ; i < data_len + 16 ; i++) { + check_sum += pkt->hdr->data[i]; + } + + pkt->hdr->data[data_len + 16] = (check_sum >> 8) & 0xff; + pkt->hdr->data[data_len + 17] = check_sum & 0xff; + pkt->hdr->data[data_len + 18] = 0x88; + pkt->hdr->data[data_len + 19] = (check_sum >> 8) & 0xff; + pkt->hdr->data[data_len + 20] = check_sum & 0xff; + pkt->hdr->data[data_len + 21] = 0x88; + + pkt->hdr->size = data_len + 22; + pkt->newflag = 1; + } else if (para->video_codec_type == VIDEO_DEC_FORMAT_WVC1) { + if ((pkt->hdr != NULL) && (pkt->hdr->data != NULL)) { + free(pkt->hdr->data); + pkt->hdr->data = NULL; + } + + if (pkt->hdr == NULL) { + pkt->hdr = (hdr_buf_t*)malloc(sizeof(hdr_buf_t)); + if (!pkt->hdr) { + CLog::Log(LOGDEBUG, "[wvc1_prefix] NOMEM!"); + return PLAYER_FAILED; + } + + pkt->hdr->data = NULL; + pkt->hdr->size = 0; + } + + pkt->hdr->data = (char*)malloc(4); + if (pkt->hdr->data == NULL) { + CLog::Log(LOGDEBUG, "[wvc1_prefix] NOMEM!"); + return PLAYER_FAILED; + } + + pkt->hdr->data[0] = 0; + pkt->hdr->data[1] = 0; + pkt->hdr->data[2] = 1; + pkt->hdr->data[3] = 0xd; + pkt->hdr->size = 4; + pkt->newflag = 1; + } + } + else if (para->video_format == VFORMAT_VP9) + vp9_update_frame_header(pkt); + + } + return PLAYER_SUCCESS; +} + +/*************************************************************************/ +CAMLCodec::CAMLCodec(CProcessInfo &processInfo) + : m_opened(false) + , m_ptsIs64us(false) + , m_speed(DVD_PLAYSPEED_NORMAL) + , m_cur_pts(INT64_0) + , m_last_pts(0) + , m_ptsOverflow(0) + , m_bufferIndex(-1) + , m_state(0) + , m_frameSizeSum(0) + , m_processInfo(processInfo) +{ + am_private = new am_private_t(); + m_dll = new DllLibAmCodec; + if(!m_dll->Load()) + CLog::Log(LOGWARNING, "CAMLCodec::CAMLCodec libamcodec.so not found"); + am_private->m_dll = m_dll; + am_private->vcodec.handle = -1; //init to invalid + am_private->vcodec.cntl_handle = -1; + am_private->vcodec.sub_handle = -1; + am_private->vcodec.audio_utils_handle = -1; +} + + +CAMLCodec::~CAMLCodec() +{ + delete am_private; + am_private = NULL; + delete m_dll, m_dll = NULL; +} + +float CAMLCodec::OMXPtsToSeconds(int omxpts) +{ + return static_cast(omxpts) / PTS_FREQ; +} + +int CAMLCodec::OMXDurationToNs(int duration) +{ + return static_cast(static_cast(duration) / PTS_FREQ * 1000000 ); +} + +int CAMLCodec::GetAmlDuration() const +{ + return am_private ? (am_private->video_rate * PTS_FREQ) / UNIT_FREQ : 0; +}; + +bool CAMLCodec::OpenDecoder(CDVDStreamInfo &hints) +{ + m_speed = DVD_PLAYSPEED_NORMAL; + m_drain = false; + m_cur_pts = INT64_0; + m_dst_rect.SetRect(0, 0, 0, 0); + m_zoom = -1.0f; + m_contrast = -1; + m_brightness = -1; + m_hints = hints; + m_state = 0; + m_frameSizes.clear(); + m_frameSizeSum = 0; + + if (!OpenAmlVideo(hints)) + { + CLog::Log(LOGERROR, "CAMLCodec::OpenDecoder - cannot open amlvideo device"); + return false; + } + + ShowMainVideo(false); + + am_packet_init(&am_private->am_pkt); + // default stream type + am_private->stream_type = AM_STREAM_ES; + // handle hints. + am_private->video_width = hints.width; + am_private->video_height = hints.height; + am_private->video_codec_id = hints.codec; + am_private->video_codec_tag = hints.codec_tag; + + // FIXME + // am_private->video_pid = hints.pid; + + // handle video ratio + AVRational video_ratio = av_d2q(1, SHRT_MAX); + //if (!hints.forced_aspect) + // video_ratio = av_d2q(hints.aspect, SHRT_MAX); + am_private->video_ratio = ((int32_t)video_ratio.num << 16) | video_ratio.den; + am_private->video_ratio64 = ((int64_t)video_ratio.num << 32) | video_ratio.den; + + // handle video rate + if (hints.fpsrate > 0 && hints.fpsscale != 0) + { + // then ffmpeg avg_frame_rate next + am_private->video_rate = 0.5 + (float)UNIT_FREQ * hints.fpsscale / hints.fpsrate; + } + + // check for 1920x1080, interlaced, 25 fps + // incorrectly reported as 50 fps (yes, video_rate == 1920) + if (hints.width == 1920 && am_private->video_rate == 1920) + { + CLog::Log(LOGDEBUG, "CAMLCodec::OpenDecoder video_rate exception"); + am_private->video_rate = 0.5 + (float)UNIT_FREQ * 1001 / 25000; + } + + // check for SD h264 content incorrectly reported as 60 fsp + // mp4/avi containers :( + if (hints.codec == AV_CODEC_ID_H264 && hints.width <= 720 && am_private->video_rate == 1602) + { + CLog::Log(LOGDEBUG, "CAMLCodec::OpenDecoder video_rate exception"); + am_private->video_rate = 0.5 + (float)UNIT_FREQ * 1001 / 24000; + } + + // check for SD h264 content incorrectly reported as some form of 30 fsp + // mp4/avi containers :( + if (hints.codec == AV_CODEC_ID_H264 && hints.width <= 720) + { + if (am_private->video_rate >= 3200 && am_private->video_rate <= 3210) + { + CLog::Log(LOGDEBUG, "CAMLCodec::OpenDecoder video_rate exception"); + am_private->video_rate = 0.5 + (float)UNIT_FREQ * 1001 / 24000; + } + } + + // handle orientation + am_private->video_rotation_degree = 0; + if (hints.orientation == 90) + am_private->video_rotation_degree = 1; + else if (hints.orientation == 180) + am_private->video_rotation_degree = 2; + else if (hints.orientation == 270) + am_private->video_rotation_degree = 3; + // handle extradata + am_private->video_format = codecid_to_vformat(hints.codec); + if ((am_private->video_format == VFORMAT_H264) + && (hints.width > 1920 || hints.height > 1088) + && (aml_support_h264_4k2k() == AML_HAS_H264_4K2K)) + { + am_private->video_format = VFORMAT_H264_4K2K; + } + switch (am_private->video_format) + { + default: + am_private->extradata = hints.extradata; + break; + case VFORMAT_REAL: + case VFORMAT_MPEG12: + break; + } + + if (am_private->stream_type == AM_STREAM_ES && am_private->video_codec_tag != 0) + am_private->video_codec_type = codec_tag_to_vdec_type(am_private->video_codec_tag); + if (am_private->video_codec_type == VIDEO_DEC_FORMAT_UNKNOW) + am_private->video_codec_type = codec_tag_to_vdec_type(am_private->video_codec_id); + + CLog::Log(LOGDEBUG, "CAMLCodec::OpenDecoder " + "hints.width({:d}), hints.height({:d}), hints.codec({:d}), hints.codec_tag({:d})", + hints.width, hints.height, hints.codec, hints.codec_tag); + CLog::Log(LOGDEBUG, "CAMLCodec::OpenDecoder hints.fpsrate({:d}), hints.fpsscale({:d}), video_rate({:d})", + hints.fpsrate, hints.fpsscale, am_private->video_rate); + CLog::Log(LOGDEBUG, "CAMLCodec::OpenDecoder hints.aspect({:f}), video_ratio.num({:d}), video_ratio.den({:d})", + hints.aspect, video_ratio.num, video_ratio.den); + CLog::Log(LOGDEBUG, "CAMLCodec::OpenDecoder hints.orientation({:d}), hints.forced_aspect({:d}), hints.extrasize({:d})", + hints.orientation, hints.forced_aspect, hints.extradata.GetSize()); + + // default video codec params + am_private->gcodec.noblock = 0; + am_private->gcodec.video_pid = am_private->video_pid; + am_private->gcodec.video_type = am_private->video_format; + am_private->gcodec.stream_type = STREAM_TYPE_ES_VIDEO; + am_private->gcodec.format = am_private->video_codec_type; + am_private->gcodec.width = am_private->video_width; + am_private->gcodec.height = am_private->video_height; + am_private->gcodec.rate = am_private->video_rate; + am_private->gcodec.ratio = am_private->video_ratio; + am_private->gcodec.ratio64 = am_private->video_ratio64; + am_private->gcodec.param = NULL; + + switch(am_private->video_format) + { + default: + break; + case VFORMAT_MPEG4: + am_private->gcodec.param = (void*)EXTERNAL_PTS; + if (m_hints.ptsinvalid) + am_private->gcodec.param = (void*)(EXTERNAL_PTS | KEYFRAME_PTS_ONLY); + break; + case VFORMAT_H264: + case VFORMAT_H264MVC: + am_private->gcodec.format = VIDEO_DEC_FORMAT_H264; + am_private->gcodec.param = (void*)EXTERNAL_PTS; + // h264 in an avi file + if (m_hints.ptsinvalid) + am_private->gcodec.param = (void*)(EXTERNAL_PTS | SYNC_OUTSIDE); + break; + case VFORMAT_H264_4K2K: + am_private->gcodec.format = VIDEO_DEC_FORMAT_H264_4K2K; + am_private->gcodec.param = (void*)EXTERNAL_PTS; + // h264 in an avi file + if (m_hints.ptsinvalid) + am_private->gcodec.param = (void*)(EXTERNAL_PTS | SYNC_OUTSIDE); + break; + case VFORMAT_REAL: + am_private->stream_type = AM_STREAM_RM; + am_private->vcodec.noblock = 1; + am_private->vcodec.stream_type = STREAM_TYPE_RM; + am_private->vcodec.am_sysinfo.ratio = 0x100; + am_private->vcodec.am_sysinfo.ratio64 = 0; + { + static unsigned short tbl[9] = {0}; + if (VIDEO_DEC_FORMAT_REAL_8 == am_private->video_codec_type) + { + am_private->gcodec.extra = am_private->extradata.GetData()[1] & 7; + tbl[0] = (((am_private->gcodec.width >> 2) - 1) << 8) + | (((am_private->gcodec.height >> 2) - 1) & 0xff); + unsigned int j; + for (unsigned int i = 1; i <= am_private->gcodec.extra; i++) + { + j = 2 * (i - 1); + tbl[i] = ((am_private->extradata.GetData()[8 + j] - 1) << 8) | ((am_private->extradata.GetData()[8 + j + 1] - 1) & 0xff); + } + } + am_private->gcodec.param = &tbl; + } + break; + case VFORMAT_VC1: + // vc1 in an avi file + if (m_hints.ptsinvalid) + am_private->gcodec.param = (void*)KEYFRAME_PTS_ONLY; + break; + case VFORMAT_HEVC: + am_private->gcodec.format = VIDEO_DEC_FORMAT_HEVC; + am_private->gcodec.param = (void*)EXTERNAL_PTS; + if (m_hints.ptsinvalid) + am_private->gcodec.param = (void*)(EXTERNAL_PTS | SYNC_OUTSIDE); + break; + case VFORMAT_VP9: + am_private->gcodec.format = VIDEO_DEC_FORMAT_VP9; + am_private->gcodec.param = (void*)EXTERNAL_PTS; + if (m_hints.ptsinvalid) + am_private->gcodec.param = (void*)(EXTERNAL_PTS | SYNC_OUTSIDE); + break; + } + am_private->gcodec.param = (void *)((std::uintptr_t)am_private->gcodec.param | (am_private->video_rotation_degree << 16)); + + // translate from generic to firmware version dependent + m_dll->codec_init_para(&am_private->gcodec, &am_private->vcodec); + + int ret = m_dll->codec_init(&am_private->vcodec); + if (ret != CODEC_ERROR_NONE) + { + CLog::Log(LOGDEBUG, "CAMLCodec::OpenDecoder codec init failed, ret=0x{:x}", -ret); + return false; + } + + am_private->dumpdemux = false; + dumpfile_open(am_private); + + m_dll->codec_pause(&am_private->vcodec); + + m_dll->codec_set_cntl_mode(&am_private->vcodec, TRICKMODE_NONE); + m_dll->codec_set_video_delay_limited_ms(&am_private->vcodec, 1000); + + m_dll->codec_set_cntl_avthresh(&am_private->vcodec, AV_SYNC_THRESH); + m_dll->codec_set_cntl_syncthresh(&am_private->vcodec, 0); + // disable tsync, we are playing video disconnected from audio. + SysfsUtils::SetInt("/sys/class/tsync/enable", 0); + + am_private->am_pkt.codec = &am_private->vcodec; + pre_header_feeding(am_private, &am_private->am_pkt); + + m_display_rect = CRect(0, 0, CDisplaySettings::GetInstance().GetCurrentResolutionInfo().iWidth, CDisplaySettings::GetInstance().GetCurrentResolutionInfo().iHeight); + + std::string strScaler; + SysfsUtils::GetString("/sys/class/ppmgr/ppscaler", strScaler); + if (strScaler.find("enabled") == std::string::npos) // Scaler not enabled, use screen size + m_display_rect = CRect(0, 0, CDisplaySettings::GetInstance().GetCurrentResolutionInfo().iScreenWidth, CDisplaySettings::GetInstance().GetCurrentResolutionInfo().iScreenHeight); + + SysfsUtils::SetInt("/sys/class/video/freerun_mode", 1); + + + struct utsname un; + if (uname(&un) == 0) + { + int linuxversion[2]; + sscanf(un.release,"%d.%d", &linuxversion[0], &linuxversion[1]); + if (linuxversion[0] > 3 || (linuxversion[0] == 3 && linuxversion[1] >= 14)) + m_ptsIs64us = true; + } + + CLog::Log(LOGNOTICE, "CAMLCodec::OpenDecoder - using V4L2 pts format: {}", m_ptsIs64us ? "64Bit":"32Bit"); + + m_ptsOverflow = 0; + + m_opened = true; + // vcodec is open, update speed if it was + // changed before VideoPlayer called OpenDecoder. + SetSpeed(m_speed); + SetPollDevice(am_private->vcodec.cntl_handle); + + return true; +} + +bool CAMLCodec::OpenAmlVideo(const CDVDStreamInfo &hints) +{ + PosixFilePtr amlVideoFile = std::make_shared(); + if (!amlVideoFile->Open("/dev/video10", O_RDONLY | O_NONBLOCK)) + { + CLog::Log(LOGERROR, "CAMLCodec::OpenAmlVideo - cannot open V4L amlvideo device /dev/video10: {}", strerror(errno)); + return false; + } + + m_amlVideoFile = amlVideoFile; + + m_defaultVfmMap = GetVfmMap("default"); + SetVfmMap("default", "decoder ppmgr deinterlace amlvideo amvideo"); + + SysfsUtils::SetInt("/sys/module/amlvideodri/parameters/freerun_mode", 3); + + return true; +} + +std::string CAMLCodec::GetVfmMap(const std::string &name) +{ + std::string vfmMap; + SysfsUtils::GetString("/sys/class/vfm/map", vfmMap); + std::vector sections = StringUtils::Split(vfmMap, '\n'); + std::string sectionMap; + for (size_t i = 0; i < sections.size(); ++i) + { + if (StringUtils::StartsWith(sections[i], name + " {")) + { + sectionMap = sections[i]; + break; + } + } + + int openingBracePos = sectionMap.find('{') + 1; + sectionMap = sectionMap.substr(openingBracePos, sectionMap.size() - openingBracePos - 1); + StringUtils::Replace(sectionMap, "(0)", ""); + + return sectionMap; +} + +void CAMLCodec::SetVfmMap(const std::string &name, const std::string &map) +{ + SysfsUtils::SetString("/sys/class/vfm/map", "rm " + name); + SysfsUtils::SetString("/sys/class/vfm/map", "add " + name + " " + map); +} + +void CAMLCodec::CloseDecoder() +{ + CLog::Log(LOGDEBUG, "CAMLCodec::CloseDecoder"); + + SetPollDevice(-1); + + // never leave vcodec ff/rw or paused. + if (m_speed != DVD_PLAYSPEED_NORMAL) + { + //m_dll->codec_resume(&am_private->vcodec); + m_dll->codec_set_cntl_mode(&am_private->vcodec, TRICKMODE_NONE); + } + m_dll->codec_close(&am_private->vcodec); + dumpfile_close(am_private); + m_opened = false; + + am_packet_release(&am_private->am_pkt); + am_private->extradata = {}; + // return tsync to default so external apps work + SysfsUtils::SetInt("/sys/class/tsync/enable", 1); + + ShowMainVideo(false); + + CloseAmlVideo(); +} + +void CAMLCodec::CloseAmlVideo() +{ + m_amlVideoFile.reset(); + SetVfmMap("default", m_defaultVfmMap); +} + +void CAMLCodec::Reset() +{ + CLog::Log(LOGDEBUG, "CAMLCodec::Reset"); + + if (!m_opened) + return; + + SetPollDevice(-1); + + // set the system blackout_policy to leave the last frame showing + int blackout_policy = 0; + SysfsUtils::GetInt("/sys/class/video/blackout_policy", blackout_policy); + SysfsUtils::SetInt("/sys/class/video/blackout_policy", 0); + + // restore the speed (some amcodec versions require this) + if (m_speed != DVD_PLAYSPEED_NORMAL) + { + m_dll->codec_set_cntl_mode(&am_private->vcodec, TRICKMODE_NONE); + } + m_dll->codec_pause(&am_private->vcodec); + + // reset the decoder + m_dll->codec_reset(&am_private->vcodec); + m_dll->codec_set_video_delay_limited_ms(&am_private->vcodec, 1000); + + dumpfile_close(am_private); + dumpfile_open(am_private); + + // re-init our am_pkt + am_packet_release(&am_private->am_pkt); + am_packet_init(&am_private->am_pkt); + am_private->am_pkt.codec = &am_private->vcodec; + pre_header_feeding(am_private, &am_private->am_pkt); + + // restore the saved system blackout_policy value + SysfsUtils::SetInt("/sys/class/video/blackout_policy", blackout_policy); + + // reset some interal vars + m_cur_pts = INT64_0; + m_ptsOverflow = 0; + m_state = 0; + m_frameSizes.clear(); + m_frameSizeSum = 0; + + SetSpeed(m_speed); + + SetPollDevice(am_private->vcodec.cntl_handle); +} + +bool CAMLCodec::AddData(uint8_t *pData, size_t iSize, double dts, double pts) +{ + if (!m_opened || !pData) + return false; + + m_frameSizes.push_back(iSize); + m_frameSizeSum += iSize; + + am_private->am_pkt.data = pData; + am_private->am_pkt.data_size = iSize; + + am_private->am_pkt.isvalid = 1; + am_private->am_pkt.avduration = 0; + + // handle pts, including 31bit wrap, aml can only handle 31 + // bit pts as it uses an int in kernel. + if (m_hints.ptsinvalid || pts == DVD_NOPTS_VALUE) + am_private->am_pkt.avpts = INT64_0; + else + { + am_private->am_pkt.avpts = 0.5 + (pts * PTS_FREQ) / DVD_TIME_BASE; + m_state |= STATE_HASPTS; + } + + // handle dts, including 31bit wrap, aml can only handle 31 + // bit dts as it uses an int in kernel. + if (dts == DVD_NOPTS_VALUE) + am_private->am_pkt.avdts = am_private->am_pkt.avpts; + else + { + am_private->am_pkt.avdts = 0.5 + (dts * PTS_FREQ) / DVD_TIME_BASE; + + // For VC1 AML decoder uses PTS only on I-Frames + if (am_private->am_pkt.avpts == INT64_0 && (((size_t)am_private->gcodec.param) & KEYFRAME_PTS_ONLY)) + am_private->am_pkt.avpts = am_private->am_pkt.avdts; + } + + //Handle PTS overflow for arm + if (sizeof(long) < 8) + { + if (am_private->am_pkt.avpts != INT64_0) + { + m_ptsOverflow = am_private->am_pkt.avpts & 0xFFFF80000000ULL; + am_private->am_pkt.avpts &= 0x7FFFFFFF; + } + if (am_private->am_pkt.avdts != INT64_0) + { + m_ptsOverflow = am_private->am_pkt.avdts & 0xFFFF80000000ULL; + am_private->am_pkt.avdts &= 0x7FFFFFFF; + } + } + + // We use this to determine the fill state if no PTS is given + if (m_cur_pts == INT64_0) + { + m_cur_pts = am_private->am_pkt.avdts; + // No PTS given -> use first DTS for AML ptsserver initialization + if ((m_state & STATE_HASPTS) == 0) + am_private->am_pkt.avpts = am_private->am_pkt.avdts; + } + + // some formats need header/data tweaks. + // the actual write occurs once in write_av_packet + // and is controlled by am_pkt.newflag. + set_header_info(am_private); + + // loop until we write all into codec, am_pkt.isvalid + // will get set to zero once everything is consumed. + // PLAYER_SUCCESS means all is ok, not all bytes were written. + int loop = 0; + while (am_private->am_pkt.isvalid && loop < 100) + { + // abort on any errors. + if (write_av_packet(am_private, &am_private->am_pkt) != PLAYER_SUCCESS) + break; + + if (am_private->am_pkt.isvalid) + CLog::Log(LOGDEBUG, "CAMLCodec::Decode: write_av_packet looping"); + loop++; + } + if (loop == 100) + { + // Decoder got stuck; Reset + Reset(); + return false; + } + + CLog::Log(LOGDEBUG, LOGVIDEO, "CAMLCodec::AddData: sz: {:u}, dts_in: {:.4lf}[{:llX}], pts_in: {:.4lf}[{:llX}], overflow:{:llx}", + static_cast(iSize), + dts / DVD_TIME_BASE, am_private->am_pkt.avdts, + pts / DVD_TIME_BASE, am_private->am_pkt.avpts, + m_ptsOverflow + ); + return true; +} + +int CAMLCodec::m_pollDevice; + +int CAMLCodec::PollFrame() +{ + std::lock_guard lock(pollSyncMutex); + if (m_pollDevice < 0) + return 0; + + struct pollfd codec_poll_fd[1]; + + codec_poll_fd[0].fd = m_pollDevice; + codec_poll_fd[0].events = POLLOUT; + + if (poll(codec_poll_fd, 1, 100) > 0) + { + g_aml_sync_event.Set(); + return 1; + } + return 0; +} + +void CAMLCodec::SetPollDevice(int dev) +{ + std::lock_guard lock(pollSyncMutex); + m_pollDevice = dev; +} + +int CAMLCodec::ReleaseFrame(const uint32_t index, bool drop) +{ + int ret; + v4l2_buffer vbuf = { 0 }; + vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vbuf.index = index; + + if (!m_amlVideoFile) + return 0; + + if (drop) + vbuf.flags |= V4L2_BUF_FLAG_DONE; + + CLog::Log(LOGDEBUG, LOGVIDEO, "CAMLCodec::ReleaseFrame idx:{:d}, drop:{:d}", index, static_cast(drop)); + + if ((ret = m_amlVideoFile->IOControl(VIDIOC_QBUF, &vbuf)) < 0) + CLog::Log(LOGERROR, "CAMLCodec::ReleaseFrame - VIDIOC_QBUF failed: {}", strerror(errno)); + return ret; +} + +int CAMLCodec::DequeueBuffer() +{ + v4l2_buffer vbuf = { 0 }; + vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + //Driver change from 10 to 0ms latency, throttle here + std::chrono::time_point now(std::chrono::system_clock::now()); + + unsigned int waitTime(10); +DRAIN: + if (m_amlVideoFile->IOControl(VIDIOC_DQBUF, &vbuf) < 0) + { + if (errno != EAGAIN) + CLog::Log(LOGERROR, "CAMLCodec::DequeueBuffer - VIDIOC_DQBUF failed: {}", strerror(errno)); + + std::chrono::milliseconds elapsed(std::chrono::duration_cast(std::chrono::system_clock::now() - now).count()); + + if (elapsed < std::chrono::milliseconds(waitTime)) + std::this_thread::sleep_for(std::chrono::milliseconds(waitTime) - elapsed); + + if (m_drain && elapsed < std::chrono::milliseconds(100)) + { + waitTime += 10; + goto DRAIN; + } + + return -errno; + } + + // Since kernel 3.14 Amlogic changed length and units of PTS values reported here. + // To differentiate such PTS values we check for existence of omx_pts_interval_lower + // parameter, because it was introduced since kernel 3.14. + m_last_pts = m_cur_pts; + + if (m_ptsIs64us) + { + m_cur_pts = vbuf.timestamp.tv_sec & 0xFFFFFFFF; + m_cur_pts <<= 32; + m_cur_pts += vbuf.timestamp.tv_usec & 0xFFFFFFFF; + m_cur_pts = (m_cur_pts * PTS_FREQ) / DVD_TIME_BASE; + } + else + { + m_cur_pts = vbuf.timestamp.tv_usec; + } + m_bufferIndex = vbuf.index; + return 0; +} + +float CAMLCodec::GetTimeSize() +{ + struct buf_status bs; + m_dll->codec_get_vbuf_state(&am_private->vcodec, &bs); + + //CLog::Log(LOGDEBUG, "CAMLCodec::Decode: buf status: s:{:d} dl:{:d} fl:{:d} rp:{:d} wp:{:d}",bs.size, bs.data_len, bs.free_len, bs.read_pointer, bs.write_pointer); + while (m_frameSizeSum > (unsigned int)bs.data_len) + { + m_frameSizeSum -= m_frameSizes.front(); + m_frameSizes.pop_front(); + } + if (bs.free_len < (bs.data_len >> 1)) + return 7.0; + + return (float)(m_frameSizes.size() * am_private->video_rate) / UNIT_FREQ; +} + +CDVDVideoCodec::VCReturn CAMLCodec::GetPicture(VideoPicture *pVideoPicture) +{ + if (!m_opened) + return CDVDVideoCodec::VC_ERROR; + + float timesize(GetTimeSize()); + if(!m_drain && timesize < 0.2) + return CDVDVideoCodec::VC_BUFFER; + + if (DequeueBuffer() == 0) + { + pVideoPicture->iFlags = 0; + + if (m_last_pts <= 0) + pVideoPicture->iDuration = static_cast(am_private->video_rate * DVD_TIME_BASE) / UNIT_FREQ; + else + pVideoPicture->iDuration = static_cast((0x7FFFFFFF & (m_cur_pts - m_last_pts)) * DVD_TIME_BASE) / PTS_FREQ; + + pVideoPicture->dts = DVD_NOPTS_VALUE; + pVideoPicture->pts = static_cast(m_cur_pts + m_ptsOverflow) / PTS_FREQ * DVD_TIME_BASE; + + CLog::Log(LOGDEBUG, LOGVIDEO, "CAMLCodec::GetPicture: index: {:d}, pts: {:.4f}[{:X}], overflow: {:X}",m_bufferIndex, pVideoPicture->pts/DVD_TIME_BASE, m_cur_pts, m_ptsOverflow); + + return CDVDVideoCodec::VC_PICTURE; + } + else if (m_drain) + return CDVDVideoCodec::VC_EOF; + else + { + if (timesize < 1.0) + return CDVDVideoCodec::VC_BUFFER; + usleep(5000); + } + + return CDVDVideoCodec::VC_NONE; +} + +void CAMLCodec::SetSpeed(int speed) +{ + if (m_speed == speed) + return; + + CLog::Log(LOGDEBUG, "CAMLCodec::SetSpeed, speed({:d})", speed); + + // update internal vars regardless + // of if we are open or not. + m_speed = speed; + + if (!m_opened) + return; + + switch(speed) + { + case DVD_PLAYSPEED_PAUSE: + //m_dll->codec_pause(&am_private->vcodec); + m_dll->codec_set_cntl_mode(&am_private->vcodec, TRICKMODE_NONE); + break; + case DVD_PLAYSPEED_NORMAL: + //m_dll->codec_resume(&am_private->vcodec); + m_dll->codec_set_cntl_mode(&am_private->vcodec, TRICKMODE_NONE); + break; + default: + //m_dll->codec_resume(&am_private->vcodec); + if ((am_private->video_format == VFORMAT_H264) || (am_private->video_format == VFORMAT_H264_4K2K)) + m_dll->codec_set_cntl_mode(&am_private->vcodec, TRICKMODE_FFFB); + else + m_dll->codec_set_cntl_mode(&am_private->vcodec, TRICKMODE_I); + break; + } +} + +void CAMLCodec::ShowMainVideo(const bool show) +{ + static int saved_disable_video = -1; + + int disable_video = show ? 0:1; + if (saved_disable_video == disable_video) + return; + + SysfsUtils::SetInt("/sys/class/video/disable_video", disable_video); + saved_disable_video = disable_video; +} + +void CAMLCodec::SetVideoZoom(const float zoom) +{ + // input zoom range is 0.5 to 2.0 with a default of 1.0. + // output zoom range is 2 to 300 with default of 100. + // we limit that to a range of 50 to 200 with default of 100. + SysfsUtils::SetInt("/sys/class/video/zoom", (int)(100 * zoom)); +} + +void CAMLCodec::SetVideoContrast(const int contrast) +{ + // input contrast range is 0 to 100 with default of 50. + // output contrast range is -127 to 127 with default of 0. + int aml_contrast = (127 * (contrast - 50)) / 50; + SysfsUtils::SetInt("/sys/class/video/contrast", aml_contrast); +} +void CAMLCodec::SetVideoBrightness(const int brightness) +{ + // input brightness range is 0 to 100 with default of 50. + // output brightness range is -127 to 127 with default of 0. + int aml_brightness = (127 * (brightness - 50)) / 50; + SysfsUtils::SetInt("/sys/class/video/brightness", aml_brightness); +} +void CAMLCodec::SetVideoSaturation(const int saturation) +{ + // output saturation range is -127 to 127 with default of 127. + SysfsUtils::SetInt("/sys/class/video/saturation", saturation); +} + +void CAMLCodec::SetVideoRect(const CRect &SrcRect, const CRect &DestRect) +{ + // this routine gets called every video frame + // and is in the context of the renderer thread so + // do not do anything stupid here. + bool update = false; + + // video zoom adjustment. + float zoom = m_processInfo.GetVideoSettings().m_CustomZoomAmount; + if ((int)(zoom * 1000) != (int)(m_zoom * 1000)) + { + m_zoom = zoom; + } + // video contrast adjustment. + int contrast = m_processInfo.GetVideoSettings().m_Contrast; + if (contrast != m_contrast) + { + SetVideoContrast(contrast); + m_contrast = contrast; + } + // video brightness adjustment. + int brightness = m_processInfo.GetVideoSettings().m_Brightness; + if (brightness != m_brightness) + { + SetVideoBrightness(brightness); + m_brightness = brightness; + } + // video rate adjustment. + unsigned int video_rate = GetDecoderVideoRate(); + if (video_rate > 0 && video_rate != am_private->video_rate) + { + CLog::Log(LOGDEBUG, "CAMLCodec::SetVideoRect: decoder fps has changed, video_rate adjusted from {:d} to {:d}", am_private->video_rate, video_rate); + am_private->video_rate = video_rate; + } + + // video view mode + int view_mode = m_processInfo.GetVideoSettings().m_ViewMode; + if (m_view_mode != view_mode) + { + m_view_mode = view_mode; + update = true; + } + + // GUI stereo mode/view. + const RenderStereoMode guiStereoMode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode(); + if (m_guiStereoMode != guiStereoMode) + { + m_guiStereoMode = guiStereoMode; + update = true; + } + RenderStereoView guiStereoView = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoView(); + if (m_guiStereoView != guiStereoView) + { + // left/right/top/bottom eye, + // this might change every other frame. + // we do not care but just track it. + m_guiStereoView = guiStereoView; + } + + // dest_rect + CRect dst_rect = DestRect; + // handle orientation + switch (am_private->video_rotation_degree) + { + case 0: + case 2: + break; + + case 1: + case 3: + { + double scale = static_cast(dst_rect.Height()) / dst_rect.Width(); + int diff = (int) ((dst_rect.Height()*scale - dst_rect.Width()) / 2); + dst_rect = CRect(DestRect.x1 - diff, DestRect.y1, DestRect.x2 + diff, DestRect.y2); + } + + } + + if (m_dst_rect != dst_rect) + { + m_dst_rect = dst_rect; + update = true; + } + + RESOLUTION video_res = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(); + if (m_video_res != video_res) + { + m_video_res = video_res; + update = true; + } + + if (!update) + { + // mainvideo 'should' be showing already if we get here, make sure. + ShowMainVideo(true); + return; + } + + CRect gui, display; + + const RESOLUTION_INFO& video_res_info = CDisplaySettings::GetInstance().GetResolutionInfo(video_res); + display = m_display_rect = CRect(0, 0, video_res_info.iScreenWidth, video_res_info.iScreenHeight); + gui = CRect(0, 0, video_res_info.iWidth, video_res_info.iHeight); + + if (gui != display) + { + float xscale = display.Width() / gui.Width(); + float yscale = display.Height() / gui.Height(); + if (m_guiStereoMode == RenderStereoMode::SPLIT_VERTICAL) + xscale /= 2.0; + else if (m_guiStereoMode == RenderStereoMode::SPLIT_HORIZONTAL) + yscale /= 2.0; + dst_rect.x1 *= xscale; + dst_rect.x2 *= xscale; + dst_rect.y1 *= yscale; + dst_rect.y2 *= yscale; + } + + if (m_guiStereoMode == RenderStereoMode::MONO) + { + std::string videoStereoMode = m_processInfo.GetVideoStereoMode(); + if (videoStereoMode == "left_right" || videoStereoMode == "right_left") + dst_rect.x2 *= 2.0; + else if (videoStereoMode == "top_bottom" || videoStereoMode == "bottom_top") + dst_rect.y2 *= 2.0; + } + else if (m_guiStereoMode == RenderStereoMode::SPLIT_VERTICAL) + { + dst_rect.x2 *= 2.0; + } + else if (m_guiStereoMode == RenderStereoMode::SPLIT_HORIZONTAL) + { + dst_rect.y2 *= 2.0; + } + +#if 1 + std::string s_dst_rect = StringUtils::Format("{:d},{:d},{:d},{:d}", + (int)dst_rect.x1, (int)dst_rect.y1, + (int)dst_rect.Width(), (int)dst_rect.Height()); + std::string s_m_dst_rect = StringUtils::Format("{:d},{:d},{:d},{:d}", + (int)m_dst_rect.x1, (int)m_dst_rect.y1, + (int)m_dst_rect.Width(), (int)m_dst_rect.Height()); + std::string s_display = StringUtils::Format("{:d},{:d},{:d},{:d}", + (int)m_display_rect.x1, (int)m_display_rect.y1, + (int)m_display_rect.Width(), (int)m_display_rect.Height()); + std::string s_gui = StringUtils::Format("{:d},{:d},{:d},{:d}", + (int)gui.x1, (int)gui.y1, + (int)gui.Width(), (int)gui.Height()); + CLog::Log(LOGDEBUG, "CAMLCodec::SetVideoRect:display({})", s_display.c_str()); + CLog::Log(LOGDEBUG, "CAMLCodec::SetVideoRect:gui({})", s_gui.c_str()); + CLog::Log(LOGDEBUG, "CAMLCodec::SetVideoRect:m_dst_rect({})", s_m_dst_rect.c_str()); + CLog::Log(LOGDEBUG, "CAMLCodec::SetVideoRect:dst_rect({})", s_dst_rect.c_str()); + CLog::Log(LOGDEBUG, "CAMLCodec::SetVideoRect:m_guiStereoMode({:d})", m_guiStereoMode); + CLog::Log(LOGDEBUG, "CAMLCodec::SetVideoRect:m_guiStereoView({:d})", m_guiStereoView); +#endif + + // goofy 0/1 based difference in aml axis coordinates. + // fix them. + dst_rect.x2--; + dst_rect.y2--; + + char video_axis[256] = {}; + sprintf(video_axis, "%d %d %d %d", (int)dst_rect.x1, (int)dst_rect.y1, (int)dst_rect.x2, (int)dst_rect.y2); + + int screen_mode = CDisplaySettings::GetInstance().IsNonLinearStretched() ? 4 : 1; + + SysfsUtils::SetString("/sys/class/video/axis", video_axis); + SysfsUtils::SetInt("/sys/class/video/screen_mode", screen_mode); + + // we only get called once gui has changed to something + // that would show video playback, so show it. + ShowMainVideo(true); +} + +void CAMLCodec::SetVideoRate(int videoRate) +{ + if (am_private) + am_private->video_rate = videoRate; +} + +unsigned int CAMLCodec::GetDecoderVideoRate() +{ + if (m_speed != DVD_PLAYSPEED_NORMAL || m_pollDevice < 0) + return 0; + + struct vdec_status vs; + m_dll->codec_get_vdec_state(&am_private->vcodec, &vs); + if (vs.fps > 0) + return static_cast(0.5 + (static_cast(UNIT_FREQ) / static_cast(vs.fps))); + else + return 0; +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/AMLCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AMLCodec.h new file mode 100644 index 00000000000..341b9700501 --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AMLCodec.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DVDVideoCodec.h" +#include "cores/VideoPlayer/DVDStreamInfo.h" +#include "cores/IPlayer.h" +#include "windowing/Resolution.h" +#include "rendering/RenderSystem.h" +#include "utils/Geometry.h" + +#include +#include + +typedef struct am_private_t am_private_t; + +class DllLibAmCodec; + +class PosixFile; +typedef std::shared_ptr PosixFilePtr; + +class CProcessInfo; + +class CAMLCodec +{ +public: + CAMLCodec(CProcessInfo &processInfo); + virtual ~CAMLCodec(); + + bool OpenDecoder(CDVDStreamInfo &hints); + void CloseDecoder(); + void Reset(); + + bool AddData(uint8_t *pData, size_t size, double dts, double pts); + CDVDVideoCodec::VCReturn GetPicture(VideoPicture* pVideoPicture); + + void SetSpeed(int speed); + void SetDrain(bool drain){m_drain = drain;}; + void SetVideoRect(const CRect &SrcRect, const CRect &DestRect); + void SetVideoRate(int videoRate); + int GetOMXPts() const { return static_cast(m_cur_pts); } + uint32_t GetBufferIndex() const { return m_bufferIndex; }; + static float OMXPtsToSeconds(int omxpts); + static int OMXDurationToNs(int duration); + int GetAmlDuration() const; + int ReleaseFrame(const uint32_t index, bool bDrop = false); + + static int PollFrame(); + static void SetPollDevice(int device); + +private: + void ShowMainVideo(const bool show); + void SetVideoZoom(const float zoom); + void SetVideoContrast(const int contrast); + void SetVideoBrightness(const int brightness); + void SetVideoSaturation(const int saturation); + bool OpenAmlVideo(const CDVDStreamInfo &hints); + void CloseAmlVideo(); + std::string GetVfmMap(const std::string &name); + void SetVfmMap(const std::string &name, const std::string &map); + int DequeueBuffer(); + float GetTimeSize(); + unsigned int GetDecoderVideoRate(); + + DllLibAmCodec *m_dll; + bool m_opened; + bool m_ptsIs64us; + bool m_drain = false; + am_private_t *am_private; + CDVDStreamInfo m_hints; + int m_speed; + int64_t m_cur_pts; + int64_t m_last_pts; + int64_t m_ptsOverflow; + uint32_t m_bufferIndex; + + CRect m_dst_rect; + CRect m_display_rect; + + int m_view_mode = -1; + RenderStereoMode m_guiStereoMode = RenderStereoMode::OFF; + RenderStereoView m_guiStereoView = RenderStereoView::OFF; + float m_zoom = -1.0f; + int m_contrast = -1; + int m_brightness = -1; + RESOLUTION m_video_res = RES_INVALID; + + static const unsigned int STATE_PREFILLED = 1; + static const unsigned int STATE_HASPTS = 2; + + unsigned int m_state; + + PosixFilePtr m_amlVideoFile; + std::string m_defaultVfmMap; + + std::deque m_frameSizes; + std::uint32_t m_frameSizeSum; + + static std::atomic_flag m_pollSync; + static int m_pollDevice; + CProcessInfo &m_processInfo; +}; diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt index 07a0b78baf3..6e8283c395f 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt @@ -12,6 +12,13 @@ if(TARGET ffmpeg::libpostproc) list(APPEND HEADERS DVDVideoPPFFmpeg.h) endif() +if(TARGET ${APP_NAME_LC}::AML) + list(APPEND SOURCES AMLCodec.cpp + DVDVideoCodecAmlogic.cpp) + list(APPEND HEADERS AMLCodec.h + DVDVideoCodecAmlogic.h) +endif() + if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) list(APPEND SOURCES DXVA.cpp) list(APPEND HEADERS DXVA.h) diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.cpp new file mode 100644 index 00000000000..435a9735137 --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.cpp @@ -0,0 +1,457 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include + +#include "DVDCodecs/DVDFactoryCodec.h" +#include "DVDVideoCodecAmlogic.h" +#include "cores/VideoPlayer/Interface/TimingConstants.h" +#include "DVDStreamInfo.h" +#include "AMLCodec.h" +#include "ServiceBroker.h" +#include "utils/AMLUtils.h" +#include "utils/BitstreamConverter.h" +#include "utils/log.h" +#include "utils/SysfsUtils.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "threads/Thread.h" + +#define __MODULE_NAME__ "DVDVideoCodecAmlogic" + +CAMLVideoBufferPool::~CAMLVideoBufferPool() +{ + CLog::Log(LOGDEBUG, "CAMLVideoBufferPool::~CAMLVideoBufferPool: Deleting {:d} buffers", static_cast(m_videoBuffers.size()) ); + for (auto buffer : m_videoBuffers) + delete buffer; +} + +CVideoBuffer* CAMLVideoBufferPool::Get() +{ + std::unique_lock lock(m_criticalSection); + + if (m_freeBuffers.empty()) + { + m_freeBuffers.push_back(m_videoBuffers.size()); + m_videoBuffers.push_back(new CAMLVideoBuffer(static_cast(m_videoBuffers.size()))); + } + int bufferIdx(m_freeBuffers.back()); + m_freeBuffers.pop_back(); + + m_videoBuffers[bufferIdx]->Acquire(shared_from_this()); + + return m_videoBuffers[bufferIdx]; +} + +void CAMLVideoBufferPool::Return(int id) +{ + std::unique_lock lock(m_criticalSection); + if (m_videoBuffers[id]->m_amlCodec) + { + m_videoBuffers[id]->m_amlCodec->ReleaseFrame(m_videoBuffers[id]->m_bufferIndex, true); + m_videoBuffers[id]->m_amlCodec = nullptr; + } + m_freeBuffers.push_back(id); +} + +/***************************************************************************/ + +CDVDVideoCodecAmlogic::CDVDVideoCodecAmlogic(CProcessInfo &processInfo) + : CDVDVideoCodec(processInfo) + , m_pFormatName("amcodec") + , m_opened(false) + , m_codecControlFlags(0) + , m_framerate(0.0) + , m_video_rate(0) + , m_mpeg2_sequence(NULL) + , m_has_keyframe(false) + , m_bitparser(NULL) + , m_bitstream(NULL) +{ +} + +CDVDVideoCodecAmlogic::~CDVDVideoCodecAmlogic() +{ + Dispose(); +} + +std::unique_ptr CDVDVideoCodecAmlogic::Create(CProcessInfo& processInfo) +{ + return std::make_unique(processInfo); +} + +bool CDVDVideoCodecAmlogic::Register() +{ + CDVDFactoryCodec::RegisterHWVideoCodec("amlogic_dec", CDVDVideoCodecAmlogic::Create); + return true; +} + +std::atomic CDVDVideoCodecAmlogic::m_InstanceGuard(false); + +bool CDVDVideoCodecAmlogic::Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) +{ + if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEAMCODEC)) + return false; + if (hints.stills || hints.width == 0) + return false; + + if (!aml_permissions()) + { + CLog::Log(LOGERROR, "AML: no proper permission, please contact the device vendor. Skipping codec..."); + return false; + } + + // allow only 1 instance here + if (m_InstanceGuard.exchange(true)) + { + CLog::Log(LOGERROR, "CDVDVideoCodecAmlogic::Open - InstanceGuard locked\n"); + return false; + } + + m_opened = false; + + m_hints = hints; + + CLog::Log(LOGDEBUG, "CDVDVideoCodecAmlogic::Opening: codec {:d} profile:{:d} extra_size:{:d}", m_hints.codec, hints.profile, hints.extradata.GetSize()); + + switch(m_hints.codec) + { + case AV_CODEC_ID_MJPEG: + m_pFormatName = "am-mjpeg"; + break; + case AV_CODEC_ID_MPEG1VIDEO: + case AV_CODEC_ID_MPEG2VIDEO: + if (m_hints.width <= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_USEAMCODECMPEG2)) + goto FAIL; + m_mpeg2_sequence_pts = 0; + m_mpeg2_sequence = new mpeg2_sequence; + m_mpeg2_sequence->width = m_hints.width; + m_mpeg2_sequence->height = m_hints.height; + m_mpeg2_sequence->ratio = m_hints.aspect; + m_mpeg2_sequence->fps_rate = m_hints.fpsrate; + m_mpeg2_sequence->fps_scale = m_hints.fpsscale; + m_pFormatName = "am-mpeg2"; + break; + case AV_CODEC_ID_H264: + if (m_hints.width <= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_USEAMCODECH264)) + { + CLog::Log(LOGDEBUG, "CDVDVideoCodecAmlogic::h264 size check failed {:d}",CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_USEAMCODECH264)); + goto FAIL; + } + switch(hints.profile) + { + case AV_PROFILE_H264_HIGH_10: + case AV_PROFILE_H264_HIGH_10_INTRA: + case AV_PROFILE_H264_HIGH_422: + case AV_PROFILE_H264_HIGH_422_INTRA: + case AV_PROFILE_H264_HIGH_444_PREDICTIVE: + case AV_PROFILE_H264_HIGH_444_INTRA: + case AV_PROFILE_H264_CAVLC_444: + goto FAIL; + } + if ((aml_support_h264_4k2k() == AML_NO_H264_4K2K) && ((m_hints.width > 1920) || (m_hints.height > 1088))) + { + // 4K is supported only on Amlogic S802/S812 chip + goto FAIL; + } + m_pFormatName = "am-h264"; + // convert h264-avcC to h264-annex-b as h264-avcC + // under streamers can have issues when seeking. + if (m_hints.extradata && m_hints.extradata.GetData()[0] == 1) + { + m_bitstream = new CBitstreamConverter; + m_bitstream->Open(m_hints.codec, m_hints.extradata.GetData(), m_hints.extradata.GetSize(), true); + m_bitstream->ResetStartDecode(); + // make sure we do not leak the existing m_hints.extradata + m_hints.extradata = {}; + m_hints.extradata = FFmpegExtraData(m_bitstream->GetExtraSize()); + memcpy(m_hints.extradata.GetData(), m_bitstream->GetExtraData(), m_hints.extradata.GetSize()); + } + else + { + m_bitparser = new CBitstreamParser(); + m_bitparser->Open(); + } + break; + case AV_CODEC_ID_MPEG4: + case AV_CODEC_ID_MSMPEG4V2: + case AV_CODEC_ID_MSMPEG4V3: + if (m_hints.width <= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_USEAMCODECMPEG4)) + goto FAIL; + m_pFormatName = "am-mpeg4"; + break; + case AV_CODEC_ID_H263: + case AV_CODEC_ID_H263P: + case AV_CODEC_ID_H263I: + // amcodec can't handle h263 + goto FAIL; +// case AV_CODEC_ID_FLV1: +// m_pFormatName = "am-flv1"; +// break; + case AV_CODEC_ID_RV10: + case AV_CODEC_ID_RV20: + case AV_CODEC_ID_RV30: + case AV_CODEC_ID_RV40: + // m_pFormatName = "am-rv"; + // rmvb is not handled well by amcodec + goto FAIL; + case AV_CODEC_ID_VC1: + m_pFormatName = "am-vc1"; + break; + case AV_CODEC_ID_WMV3: + m_pFormatName = "am-wmv3"; + break; + case AV_CODEC_ID_AVS: + case AV_CODEC_ID_CAVS: + m_pFormatName = "am-avs"; + break; + case AV_CODEC_ID_VP9: + if (!aml_support_vp9()) + goto FAIL; + m_pFormatName = "am-vp9"; + break; + case AV_CODEC_ID_HEVC: + if (aml_support_hevc()) { + if (!aml_support_hevc_4k2k() && ((m_hints.width > 1920) || (m_hints.height > 1088))) + { + // 4K HEVC is supported only on Amlogic S812 chip + goto FAIL; + } + } else { + // HEVC supported only on S805 and S812. + goto FAIL; + } + if ((hints.profile == AV_PROFILE_HEVC_MAIN_10) && !aml_support_hevc_10bit()) + { + goto FAIL; + } + m_pFormatName = "am-h265"; + m_bitstream = new CBitstreamConverter(); + m_bitstream->Open(m_hints.codec, m_hints.extradata.GetData(), m_hints.extradata.GetSize(), true); + // make sure we do not leak the existing m_hints.extradata + m_hints.extradata = {}; + m_hints.extradata = FFmpegExtraData(m_bitstream->GetExtraSize()); + memcpy(m_hints.extradata.GetData(), m_bitstream->GetExtraData(), m_hints.extradata.GetSize()); + break; + default: + CLog::Log(LOGDEBUG, "{}: Unknown hints.codec({:d}", __MODULE_NAME__, m_hints.codec); + goto FAIL; + } + + m_aspect_ratio = m_hints.aspect; + + m_Codec = std::shared_ptr(new CAMLCodec(m_processInfo)); + if (!m_Codec) + { + CLog::Log(LOGERROR, "{}: Failed to create Amlogic Codec", __MODULE_NAME__); + goto FAIL; + } + + // allocate a dummy VideoPicture buffer. + m_videobuffer.Reset(); + + m_videobuffer.iWidth = m_hints.width; + m_videobuffer.iHeight = m_hints.height; + + m_videobuffer.iDisplayWidth = m_videobuffer.iWidth; + m_videobuffer.iDisplayHeight = m_videobuffer.iHeight; + if (m_hints.aspect > 0.0 && !m_hints.forced_aspect) + { + m_videobuffer.iDisplayWidth = ((int)lrint(m_videobuffer.iHeight * m_hints.aspect)) & ~3; + if (m_videobuffer.iDisplayWidth > m_videobuffer.iWidth) + { + m_videobuffer.iDisplayWidth = m_videobuffer.iWidth; + m_videobuffer.iDisplayHeight = ((int)lrint(m_videobuffer.iWidth / m_hints.aspect)) & ~3; + } + } + + m_processInfo.SetVideoDecoderName(m_pFormatName, true); + m_processInfo.SetVideoDimensions(m_hints.width, m_hints.height); + m_processInfo.SetVideoDeintMethod("hardware"); + m_processInfo.SetVideoDAR(m_hints.aspect); + + m_has_keyframe = false; + + CLog::Log(LOGINFO, "{}: Opened Amlogic Codec", __MODULE_NAME__); + return true; +FAIL: + Dispose(); + return false; +} + +void CDVDVideoCodecAmlogic::Dispose(void) +{ + m_videoBufferPool = nullptr; + + if (m_Codec) + m_Codec->CloseDecoder(), m_Codec = nullptr; + + m_videobuffer.iFlags = 0; + + if (m_mpeg2_sequence) + delete m_mpeg2_sequence, m_mpeg2_sequence = NULL; + + if (m_bitstream) + delete m_bitstream, m_bitstream = NULL; + + if (m_bitparser) + delete m_bitparser, m_bitparser = NULL; + + m_opened = false; + m_InstanceGuard.exchange(false); +} + +bool CDVDVideoCodecAmlogic::AddData(const DemuxPacket &packet) +{ + // Handle Input, add demuxer packet to input queue, we must accept it or + // it will be discarded as VideoPlayerVideo has no concept of "try again". + + uint8_t *pData(packet.pData); + int iSize(packet.iSize); + + if (pData) + { + if (m_bitstream) + { + if (!m_bitstream->Convert(pData, iSize)) + return true; + + if (!m_bitstream->CanStartDecode()) + { + CLog::Log(LOGDEBUG, "{}::Decode waiting for keyframe (bitstream)", __MODULE_NAME__); + return true; + } + pData = m_bitstream->GetConvertBuffer(); + iSize = m_bitstream->GetConvertSize(); + } + else if (!m_has_keyframe && m_bitparser) + { + if (!m_bitparser->CanStartDecode(pData, iSize)) + { + CLog::Log(LOGDEBUG, "{}::Decode waiting for keyframe (bitparser)", __MODULE_NAME__); + return true; + } + else + m_has_keyframe = true; + } + FrameRateTracking( pData, iSize, packet.dts, packet.pts); + + if (!m_opened) + { + if (packet.pts == DVD_NOPTS_VALUE) + m_hints.ptsinvalid = true; + + if (m_Codec && !m_Codec->OpenDecoder(m_hints)) + CLog::Log(LOGERROR, "{}: Failed to open Amlogic Codec", __MODULE_NAME__); + + m_videoBufferPool = std::shared_ptr(new CAMLVideoBufferPool()); + + m_opened = true; + } + } + + return m_Codec->AddData(pData, iSize, packet.dts, m_hints.ptsinvalid ? DVD_NOPTS_VALUE : packet.pts); +} + +void CDVDVideoCodecAmlogic::Reset(void) +{ + m_Codec->Reset(); + m_mpeg2_sequence_pts = 0; + m_has_keyframe = false; + if (m_bitstream && m_hints.codec == AV_CODEC_ID_H264) + m_bitstream->ResetStartDecode(); +} + +CDVDVideoCodec::VCReturn CDVDVideoCodecAmlogic::GetPicture(VideoPicture* pVideoPicture) +{ + if (!m_Codec) + return VC_ERROR; + + VCReturn retVal = m_Codec->GetPicture(&m_videobuffer); + + if (retVal == VC_PICTURE) + { + pVideoPicture->SetParams(m_videobuffer); + + pVideoPicture->videoBuffer = m_videoBufferPool->Get(); + static_cast(pVideoPicture->videoBuffer)->Set(this, m_Codec, + m_Codec->GetOMXPts(), m_Codec->GetAmlDuration(), m_Codec->GetBufferIndex());; + } + + // check for mpeg2 aspect ratio changes + if (m_mpeg2_sequence && pVideoPicture->pts >= m_mpeg2_sequence_pts) + m_aspect_ratio = m_mpeg2_sequence->ratio; + + pVideoPicture->iDisplayWidth = pVideoPicture->iWidth; + pVideoPicture->iDisplayHeight = pVideoPicture->iHeight; + if (m_aspect_ratio > 1.0 && !m_hints.forced_aspect) + { + pVideoPicture->iDisplayWidth = ((int)lrint(pVideoPicture->iHeight * m_aspect_ratio)) & ~3; + if (pVideoPicture->iDisplayWidth > pVideoPicture->iWidth) + { + pVideoPicture->iDisplayWidth = pVideoPicture->iWidth; + pVideoPicture->iDisplayHeight = ((int)lrint(pVideoPicture->iWidth / m_aspect_ratio)) & ~3; + } + } + + return retVal; +} + +void CDVDVideoCodecAmlogic::SetCodecControl(int flags) +{ + if (m_codecControlFlags != flags) + { + CLog::Log(LOGDEBUG, LOGVIDEO, "{} {:x}->{:x}", __func__, m_codecControlFlags, flags); + m_codecControlFlags = flags; + + if (flags & DVD_CODEC_CTRL_DROP) + m_videobuffer.iFlags |= DVP_FLAG_DROPPED; + else + m_videobuffer.iFlags &= ~DVP_FLAG_DROPPED; + + if (m_Codec) + m_Codec->SetDrain((flags & DVD_CODEC_CTRL_DRAIN) != 0); + } +} + +void CDVDVideoCodecAmlogic::SetSpeed(int iSpeed) +{ + if (m_Codec) + m_Codec->SetSpeed(iSpeed); +} + +void CDVDVideoCodecAmlogic::FrameRateTracking(uint8_t *pData, int iSize, double dts, double pts) +{ + // mpeg2 handling + if (m_mpeg2_sequence) + { + // probe demux for sequence_header_code NAL and + // decode aspect ratio and frame rate. + if (CBitstreamConverter::mpeg2_sequence_header(pData, iSize, m_mpeg2_sequence) && + (m_mpeg2_sequence->fps_rate > 0) && (m_mpeg2_sequence->fps_scale > 0)) + { + m_mpeg2_sequence_pts = pts; + if (m_mpeg2_sequence_pts == DVD_NOPTS_VALUE) + m_mpeg2_sequence_pts = dts; + + m_hints.fpsrate = m_mpeg2_sequence->fps_rate; + m_hints.fpsscale = m_mpeg2_sequence->fps_scale; + m_framerate = static_cast(m_mpeg2_sequence->fps_rate) / m_mpeg2_sequence->fps_scale; + m_video_rate = (int)(0.5 + (96000.0 / m_framerate)); + + m_hints.width = m_mpeg2_sequence->width; + m_hints.height = m_mpeg2_sequence->height; + m_hints.aspect = m_mpeg2_sequence->ratio; + + m_processInfo.SetVideoFps(m_framerate); + m_processInfo.SetVideoDAR(m_hints.aspect); + } + return; + } +} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.h new file mode 100644 index 00000000000..c1a64e4695e --- /dev/null +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DVDVideoCodec.h" +#include "DVDStreamInfo.h" +#include "threads/CriticalSection.h" +#include "cores/VideoPlayer/Buffers/VideoBuffer.h" + +#include +#include + +class CAMLCodec; +struct mpeg2_sequence; +class CBitstreamParser; +class CBitstreamConverter; + +class CDVDVideoCodecAmlogic; + +class CAMLVideoBuffer : public CVideoBuffer +{ +public: + CAMLVideoBuffer(int id) : CVideoBuffer(id) {}; + void Set(CDVDVideoCodecAmlogic *codec, std::shared_ptr amlcodec, int omxPts, int amlDuration, uint32_t bufferIndex) + { + m_codec = codec; + m_amlCodec = amlcodec; + m_omxPts = omxPts; + m_amlDuration = amlDuration; + m_bufferIndex = bufferIndex; + } + + CDVDVideoCodecAmlogic* m_codec; + std::shared_ptr m_amlCodec; + int m_omxPts, m_amlDuration; + uint32_t m_bufferIndex; +}; + +class CAMLVideoBufferPool : public IVideoBufferPool +{ +public: + virtual ~CAMLVideoBufferPool(); + + virtual CVideoBuffer* Get() override; + virtual void Return(int id) override; + +private: + CCriticalSection m_criticalSection;; + std::vector m_videoBuffers; + std::vector m_freeBuffers; +}; + +class CDVDVideoCodecAmlogic : public CDVDVideoCodec +{ +public: + CDVDVideoCodecAmlogic(CProcessInfo &processInfo); + virtual ~CDVDVideoCodecAmlogic(); + + static std::unique_ptr Create(CProcessInfo& processInfo); + static bool Register(); + + // Required overrides + virtual bool Open(CDVDStreamInfo &hints, CDVDCodecOptions &options) override; + virtual bool AddData(const DemuxPacket &packet) override; + virtual void Reset() override; + virtual VCReturn GetPicture(VideoPicture* pVideoPicture) override; + virtual void SetSpeed(int iSpeed) override; + virtual void SetCodecControl(int flags) override; + virtual const char* GetName(void) override { return (const char*)m_pFormatName; } + +protected: + void Dispose(void); + void FrameRateTracking(uint8_t *pData, int iSize, double dts, double pts); + //void RemoveInfo(CDVDAmlogicInfo* info); + + std::shared_ptr m_Codec; + + const char *m_pFormatName; + VideoPicture m_videobuffer; + bool m_opened; + int m_codecControlFlags; + CDVDStreamInfo m_hints; + double m_framerate; + int m_video_rate; + float m_aspect_ratio; + mpeg2_sequence *m_mpeg2_sequence; + double m_mpeg2_sequence_pts; + bool m_has_keyframe; + + CBitstreamParser *m_bitparser; + CBitstreamConverter *m_bitstream; +private: + std::shared_ptr m_videoBufferPool; + static std::atomic m_InstanceGuard; +}; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt index 2b9efdb7201..bff0bec2d4a 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt @@ -40,6 +40,7 @@ endif() if(TARGET ${APP_NAME_LC}::OpenGLES AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR "ios" IN_LIST CORE_PLATFORM_NAME_LC OR "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR + "aml" IN_LIST CORE_PLATFORM_NAME_LC OR "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "x11" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC)) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt index 8fcd4df51c5..abf198c4d36 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt @@ -41,6 +41,11 @@ if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) endif() endif() +if(TARGET ${APP_NAME_LC}::AML) + list(APPEND SOURCES RendererAML.cpp) + list(APPEND HEADERS RendererAML.h) +endif() + if(CORE_SYSTEM_NAME STREQUAL android) list(APPEND SOURCES RendererMediaCodec.cpp RendererMediaCodecSurface.cpp) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererAML.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererAML.cpp new file mode 100644 index 00000000000..0c98c8603a2 --- /dev/null +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererAML.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2007-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RendererAML.h" + +#include "cores/VideoPlayer/DVDCodecs/Video/AMLCodec.h" +#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.h" +#include "cores/VideoPlayer/VideoRenderers/RenderCapture.h" +#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h" +#include "cores/VideoPlayer/VideoRenderers/RenderFlags.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSettings.h" +#include "utils/ScreenshotAML.h" +#include "utils/SysfsUtils.h" +#include "utils/log.h" + +CRendererAML::CRendererAML() + : m_prevVPts(-1) + , m_bConfigured(false) +{ + CLog::Log(LOGINFO, "Constructing CRendererAML"); +} + +CRendererAML::~CRendererAML() +{ + Reset(); +} + +CBaseRenderer* CRendererAML::Create(CVideoBuffer *buffer) +{ + if (buffer && dynamic_cast(buffer)) + return new CRendererAML(); + return nullptr; +} + +bool CRendererAML::Register() +{ + VIDEOPLAYER::CRendererFactory::RegisterRenderer("amlogic", CRendererAML::Create); + return true; +} + +bool CRendererAML::Configure(const VideoPicture &picture, float fps, unsigned int orientation) +{ + m_sourceWidth = picture.iWidth; + m_sourceHeight = picture.iHeight; + m_renderOrientation = orientation; + + m_iFlags = GetFlagsChromaPosition(picture.chroma_position) | + GetFlagsColorMatrix(picture.color_space, picture.iWidth, picture.iHeight) | + GetFlagsColorPrimaries(picture.color_primaries) | + GetFlagsStereoMode(picture.stereoMode); + + // Calculate the input frame aspect ratio. + CalculateFrameAspectRatio(picture.iDisplayWidth, picture.iDisplayHeight); + SetViewMode(m_videoSettings.m_ViewMode); + ManageRenderArea(); + + m_bConfigured = true; + + return true; +} + +CRenderInfo CRendererAML::GetRenderInfo() +{ + CRenderInfo info; + info.max_buffer_size = m_numRenderBuffers; + info.opaque_pointer = (void *)this; + return info; +} + +bool CRendererAML::RenderCapture(int index, CRenderCapture* capture) +{ + capture->BeginRender(); + capture->EndRender(); + CScreenshotAML::CaptureVideoFrame((unsigned char *)capture->GetRenderBuffer(), capture->GetWidth(), capture->GetHeight()); + return true; +} + +void CRendererAML::AddVideoPicture(const VideoPicture &picture, int index) +{ + ReleaseBuffer(index); + + BUFFER &buf(m_buffers[index]); + if (picture.videoBuffer) + { + buf.videoBuffer = picture.videoBuffer; + buf.videoBuffer->Acquire(); + } +} + +void CRendererAML::ReleaseBuffer(int idx) +{ + BUFFER &buf(m_buffers[idx]); + if (buf.videoBuffer) + { + CAMLVideoBuffer *amli(dynamic_cast(buf.videoBuffer)); + if (amli) + { + if (amli->m_amlCodec) + { + amli->m_amlCodec->ReleaseFrame(amli->m_bufferIndex, true); + amli->m_amlCodec = nullptr; // Released + } + amli->Release(); + } + buf.videoBuffer = nullptr; + } +} + +bool CRendererAML::Supports(ERENDERFEATURE feature) const +{ + if (feature == RENDERFEATURE_ZOOM || + feature == RENDERFEATURE_CONTRAST || + feature == RENDERFEATURE_BRIGHTNESS || + feature == RENDERFEATURE_NONLINSTRETCH || + feature == RENDERFEATURE_VERTICAL_SHIFT || + feature == RENDERFEATURE_STRETCH || + feature == RENDERFEATURE_PIXEL_RATIO || + feature == RENDERFEATURE_ROTATION) + return true; + + return false; +} + +void CRendererAML::Reset() +{ + m_prevVPts = -1; + for (int i = 0 ; i < m_numRenderBuffers ; ++i) + { + if (m_buffers[i].videoBuffer) + { + m_buffers[i].videoBuffer->Release(); + m_buffers[i].videoBuffer = nullptr; + } + } +} + +void CRendererAML::RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha) +{ + ManageRenderArea(); + + CAMLVideoBuffer *amli = dynamic_cast(m_buffers[index].videoBuffer); + if(amli && amli->m_amlCodec) + { + int pts = amli->m_omxPts; + if (pts != m_prevVPts) + { + amli->m_amlCodec->ReleaseFrame(amli->m_bufferIndex); + amli->m_amlCodec->SetVideoRect(m_sourceRect, m_destRect); + amli->m_amlCodec = nullptr; //Mark frame as processed + m_prevVPts = pts; + } + } + CAMLCodec::PollFrame(); +} diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererAML.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererAML.h new file mode 100644 index 00000000000..87f4c46ffa4 --- /dev/null +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererAML.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2007-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "cores/VideoPlayer/VideoRenderers/BaseRenderer.h" + +class CRendererAML : public CBaseRenderer +{ +public: + CRendererAML(); + virtual ~CRendererAML(); + + // Registration + static CBaseRenderer* Create(CVideoBuffer *buffer); + static bool Register(); + + virtual bool RenderCapture(int index, CRenderCapture* capture) override; + virtual void AddVideoPicture(const VideoPicture &picture, int index) override; + virtual void ReleaseBuffer(int idx) override; + virtual bool Configure(const VideoPicture &picture, float fps, unsigned int orientation) override; + virtual bool IsConfigured() override { return m_bConfigured; }; + virtual bool ConfigChanged(const VideoPicture &picture) { return false; }; + virtual CRenderInfo GetRenderInfo() override; + virtual void UnInit() override {}; + virtual void Update() override {}; + virtual void RenderUpdate(int index, int index2, bool clear, unsigned int flags, unsigned int alpha) override; + virtual bool SupportsMultiPassRendering()override { return false; }; + + // Player functions + virtual bool IsGuiLayer() override { return false; }; + + // Feature support + virtual bool Supports(ESCALINGMETHOD method) const override { return false; }; + virtual bool Supports(ERENDERFEATURE feature) const override; + +private: + void Reset(); + + static const int m_numRenderBuffers = 4; + + struct BUFFER + { + BUFFER() : videoBuffer(nullptr) {}; + CVideoBuffer *videoBuffer; + int duration; + } m_buffers[m_numRenderBuffers]; + + int m_prevVPts; + bool m_bConfigured; +}; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt index 4cf1269214d..d86447cbc07 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt @@ -33,6 +33,7 @@ endif() if(TARGET ${APP_NAME_LC}::OpenGLES AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR "ios" IN_LIST CORE_PLATFORM_NAME_LC OR "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR + "aml" IN_LIST CORE_PLATFORM_NAME_LC OR "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "x11" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC)) diff --git a/xbmc/platform/linux/CMakeLists.txt b/xbmc/platform/linux/CMakeLists.txt index 7c4da1bd768..d0f042e4b42 100644 --- a/xbmc/platform/linux/CMakeLists.txt +++ b/xbmc/platform/linux/CMakeLists.txt @@ -40,4 +40,9 @@ if(ADDONS_CONFIGURE_AT_STARTUP) add_compile_definitions(ADDONS_CONFIGURE_AT_STARTUP) endif() +if("aml" IN_LIST CORE_PLATFORM_NAME_LC) + list(APPEND SOURCES ScreenshotSurfaceAML.cpp) + list(APPEND HEADERS ScreenshotSurfaceAML.h) +endif() + core_add_library(linuxsupport) diff --git a/xbmc/platform/linux/ScreenshotSurfaceAML.cpp b/xbmc/platform/linux/ScreenshotSurfaceAML.cpp new file mode 100644 index 00000000000..cadaca750a7 --- /dev/null +++ b/xbmc/platform/linux/ScreenshotSurfaceAML.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * Copyright (C) 2020-present Team CoreELEC (https://coreelec.org) + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "ScreenshotSurfaceAML.h" +#include "ServiceBroker.h" +#include "system_gl.h" +#include "threads/SingleLock.h" +#include "utils/Screenshot.h" +#include "utils/ScreenshotAML.h" +#include "utils/log.h" +#include "windowing/WinSystem.h" + +void CScreenshotSurfaceAML::Register() +{ + CScreenShot::Register(CScreenshotSurfaceAML::CreateSurface); +} + +std::unique_ptr CScreenshotSurfaceAML::CreateSurface() +{ + return std::unique_ptr(new CScreenshotSurfaceAML()); +} + +bool CScreenshotSurfaceAML::Capture() +{ + std::unique_lock lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + CServiceBroker::GetGUI()->GetWindowManager().Render(); + +#ifndef HAS_GLES + glReadBuffer(GL_BACK); +#endif + + //get current viewport + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + + m_width = viewport[2] - viewport[0]; + m_height = viewport[3] - viewport[1]; + m_stride = m_width * 4; + unsigned char* surface = new unsigned char[m_stride * m_height]; + + //read pixels from the backbuffer +#if HAS_GLES >= 2 + glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)surface); +#else + glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid*)surface); +#endif + + //make a new buffer and copy the read image to it with the Y axis inverted + m_buffer = new unsigned char[m_stride * m_height]; + for (int y = 0; y < m_height; y++) + { +#ifdef HAS_GLES + // we need to save in BGRA order so XOR Swap RGBA -> BGRA + unsigned char* swap_pixels = surface + (m_height - y - 1) * m_stride; + for (int x = 0; x < m_width; x++, swap_pixels+=4) + { + std::swap(swap_pixels[0], swap_pixels[2]); + } +#endif + memcpy(m_buffer + y * m_stride, surface + (m_height - y - 1) *m_stride, m_stride); + } + + delete [] surface; + + // Captures the current visible videobuffer and blend it into m_buffer (captured overlay) + CScreenshotAML::CaptureVideoFrame(m_buffer, m_width, m_height); + return true; +} diff --git a/xbmc/platform/linux/ScreenshotSurfaceAML.h b/xbmc/platform/linux/ScreenshotSurfaceAML.h new file mode 100644 index 00000000000..49ee74c7572 --- /dev/null +++ b/xbmc/platform/linux/ScreenshotSurfaceAML.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * Copyright (C) 2020-present Team CoreELEC (https://coreelec.org) + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/IScreenshotSurface.h" + +#include + +class CScreenshotSurfaceAML : public IScreenshotSurface +{ +public: + static void Register(); + static std::unique_ptr CreateSurface(); + + bool Capture() override; +}; diff --git a/xbmc/platform/linux/input/CMakeLists.txt b/xbmc/platform/linux/input/CMakeLists.txt index 32b7ce4987b..0984c99b552 100644 --- a/xbmc/platform/linux/input/CMakeLists.txt +++ b/xbmc/platform/linux/input/CMakeLists.txt @@ -6,7 +6,8 @@ if(TARGET ${APP_NAME_LC}::LircClient) list(APPEND HEADERS LIRC.h) endif() -if("gbm" IN_LIST CORE_PLATFORM_NAME_LC) +if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR + "aml" IN_LIST CORE_PLATFORM_NAME_LC) if(TARGET ${APP_NAME_LC}::LibInput) list(APPEND SOURCES LibInputHandler.cpp LibInputKeyboard.cpp @@ -22,7 +23,7 @@ if("gbm" IN_LIST CORE_PLATFORM_NAME_LC) endif() endif() -if(("gbm" IN_LIST CORE_PLATFORM_NAME_LC AND TARGET ${APP_NAME_LC}::LibInput) OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC) +if(("gbm" IN_LIST CORE_PLATFORM_NAME_LC AND TARGET ${APP_NAME_LC}::LibInput) OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC OR "aml" IN_LIST CORE_PLATFORM_NAME_LC) list(APPEND SOURCES EvdevKeyMapping.cpp) list(APPEND HEADERS EvdevKeyMapping.h XkbCompat.h) diff --git a/xbmc/settings/SettingConditions.cpp b/xbmc/settings/SettingConditions.cpp index d37204319ca..b9f036c0955 100644 --- a/xbmc/settings/SettingConditions.cpp +++ b/xbmc/settings/SettingConditions.cpp @@ -24,6 +24,9 @@ #include "settings/SettingAddon.h" #include "settings/SettingsComponent.h" #include "utils/FontUtils.h" +#if defined(HAS_LIBAMCODEC) +#include "utils/AMLUtils.h" +#endif // defined(HAS_LIBAMCODEC) #include "utils/StringUtils.h" #include "windowing/WinSystem.h" @@ -412,6 +415,10 @@ void CSettingConditions::Initialize() #ifdef TARGET_DARWIN_TVOS m_simpleConditions.emplace("have_tvos"); #endif +#ifdef HAS_LIBAMCODEC + if (aml_present()) + m_simpleConditions.insert("have_amcodec"); +#endif #if defined(TARGET_WINDOWS) m_simpleConditions.emplace("has_dx"); m_simpleConditions.emplace("hasdxva2"); diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index b9010537391..2f84edde660 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -123,6 +123,10 @@ class CSettings : public CSettingsBase, public CSettingCreator, public CSettingC static constexpr auto SETTING_VIDEOPLAYER_HQSCALERPRECISION = "videoplayer.hqscalerprecision"; static constexpr auto SETTING_VIDEOPLAYER_USESUPERRESOLUTION = "videoplayer.usesuperresolution"; static constexpr auto SETTING_VIDEOPLAYER_HIGHPRECISIONPROCESSING = "videoplayer.highprecision"; + static constexpr auto SETTING_VIDEOPLAYER_USEAMCODEC = "videoplayer.useamcodec"; + static constexpr auto SETTING_VIDEOPLAYER_USEAMCODECMPEG2 = "videoplayer.useamcodecmpeg2"; + static constexpr auto SETTING_VIDEOPLAYER_USEAMCODECMPEG4 = "videoplayer.useamcodecmpeg4"; + static constexpr auto SETTING_VIDEOPLAYER_USEAMCODECH264 = "videoplayer.useamcodech264"; static constexpr auto SETTING_VIDEOPLAYER_USEMEDIACODEC = "videoplayer.usemediacodec"; static constexpr auto SETTING_VIDEOPLAYER_USEMEDIACODECSURFACE = "videoplayer.usemediacodecsurface"; diff --git a/xbmc/utils/AMLUtils.cpp b/xbmc/utils/AMLUtils.cpp new file mode 100644 index 00000000000..62726461d85 --- /dev/null +++ b/xbmc/utils/AMLUtils.cpp @@ -0,0 +1,702 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include +#include +#include +#include +#include +#include + +#include "AMLUtils.h" +#include "utils/CPUInfo.h" +#include "utils/log.h" +#include "utils/SysfsUtils.h" +#include "utils/StringUtils.h" +#include "windowing/GraphicContext.h" +#include "utils/RegExp.h" +#include "filesystem/SpecialProtocol.h" +#include "rendering/RenderSystem.h" + +#include "linux/fb.h" +#include + + +bool aml_present() +{ + static int has_aml = -1; + if (has_aml == -1) + { + if (SysfsUtils::Has("/sys/class/audiodsp/digital_raw")) + has_aml = 1; + else + has_aml = 0; + if (has_aml) + CLog::Log(LOGNOTICE, "AML device detected"); + } + return has_aml == 1; +} + +bool aml_permissions() +{ + if (!aml_present()) + return false; + + static int permissions_ok = -1; + if (permissions_ok == -1) + { + permissions_ok = 1; + + if (!SysfsUtils::HasRW("/dev/amvideo")) + { + CLog::Log(LOGERROR, "AML: no rw on /dev/amvideo"); + permissions_ok = 0; + } + if (!SysfsUtils::HasRW("/dev/amstream_mpts")) + { + CLog::Log(LOGERROR, "AML: no rw on /dev/amstream*"); + permissions_ok = 0; + } + if (!SysfsUtils::HasRW("/sys/class/video/axis")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/video/axis"); + permissions_ok = 0; + } + if (!SysfsUtils::HasRW("/sys/class/video/screen_mode")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/video/screen_mode"); + permissions_ok = 0; + } + if (!SysfsUtils::HasRW("/sys/class/video/disable_video")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/video/disable_video"); + permissions_ok = 0; + } + if (!SysfsUtils::HasRW("/sys/class/tsync/pts_pcrscr")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/tsync/pts_pcrscr"); + permissions_ok = 0; + } + if (!SysfsUtils::HasRW("/dev/video10")) + { + CLog::Log(LOGERROR, "AML: no rw on /dev/video10"); + permissions_ok = 0; + } + if (!SysfsUtils::HasRW("/sys/module/amlvideodri/parameters/freerun_mode")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/module/amlvideodri/parameters/freerun_mode"); + permissions_ok = 0; + } + if (!SysfsUtils::HasRW("/sys/class/video/freerun_mode")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/video/freerun_mode"); + permissions_ok = 0; + } + if (!SysfsUtils::HasRW("/sys/class/audiodsp/digital_raw")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/audiodsp/digital_raw"); + } + if (!SysfsUtils::HasRW("/sys/class/amhdmitx/amhdmitx0/config")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/amhdmitx/amhdmitx0/config"); + } + if (!SysfsUtils::HasRW("/sys/class/vfm/map")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/vfm/map"); + } + if (!SysfsUtils::HasRW("/sys/class/tsync/enable")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/tsync/enable"); + } + if (!SysfsUtils::HasRW("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"); + } + if (!SysfsUtils::HasRW("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"); + } + if (!SysfsUtils::HasRW("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"); + } + if (aml_has_frac_rate_policy() && !SysfsUtils::HasRW("/sys/class/amhdmitx/amhdmitx0/frac_rate_policy")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/amhdmitx/amhdmitx0/frac_rate_policy"); + } + if (!SysfsUtils::HasRW("/sys/module/di/parameters/bypass_prog")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/module/di/parameters/bypass_prog"); + } + if (!SysfsUtils::HasRW("/sys/class/display/mode")) + { + CLog::Log(LOGERROR, "AML: no rw on /sys/class/display/mode"); + } + } + + return permissions_ok == 1; +} + +bool aml_support_hevc() +{ + static int has_hevc = -1; + + if (has_hevc == -1) + { + std::string valstr; + if(SysfsUtils::GetString("/sys/class/amstream/vcodec_profile", valstr) != 0) + has_hevc = 0; + else + has_hevc = (valstr.find("hevc:") != std::string::npos) ? 1: 0; + } + return (has_hevc == 1); +} + +bool aml_support_hevc_4k2k() +{ + static int has_hevc_4k2k = -1; + + if (has_hevc_4k2k == -1) + { + CRegExp regexp; + regexp.RegComp("hevc:.*4k"); + std::string valstr; + if (SysfsUtils::GetString("/sys/class/amstream/vcodec_profile", valstr) != 0) + has_hevc_4k2k = 0; + else + has_hevc_4k2k = (regexp.RegFind(valstr) >= 0) ? 1 : 0; + } + return (has_hevc_4k2k == 1); +} + +bool aml_support_hevc_10bit() +{ + static int has_hevc_10bit = -1; + + if (has_hevc_10bit == -1) + { + CRegExp regexp; + regexp.RegComp("hevc:.*10bit"); + std::string valstr; + if (SysfsUtils::GetString("/sys/class/amstream/vcodec_profile", valstr) != 0) + has_hevc_10bit = 0; + else + has_hevc_10bit = (regexp.RegFind(valstr) >= 0) ? 1 : 0; + } + return (has_hevc_10bit == 1); +} + +AML_SUPPORT_H264_4K2K aml_support_h264_4k2k() +{ + static AML_SUPPORT_H264_4K2K has_h264_4k2k = AML_SUPPORT_H264_4K2K_UNINIT; + + if (has_h264_4k2k == AML_SUPPORT_H264_4K2K_UNINIT) + { + std::string valstr; + if (SysfsUtils::GetString("/sys/class/amstream/vcodec_profile", valstr) != 0) + has_h264_4k2k = AML_NO_H264_4K2K; + else if (valstr.find("h264:4k") != std::string::npos) + has_h264_4k2k = AML_HAS_H264_4K2K_SAME_PROFILE; + else if (valstr.find("h264_4k2k:") != std::string::npos) + has_h264_4k2k = AML_HAS_H264_4K2K; + else + has_h264_4k2k = AML_NO_H264_4K2K; + } + return has_h264_4k2k; +} + +bool aml_support_vp9() +{ + static int has_vp9 = -1; + + if (has_vp9 == -1) + { + CRegExp regexp; + regexp.RegComp("vp9:.*compressed"); + std::string valstr; + if (SysfsUtils::GetString("/sys/class/amstream/vcodec_profile", valstr) != 0) + has_vp9 = 0; + else + has_vp9 = (regexp.RegFind(valstr) >= 0) ? 1 : 0; + } + return (has_vp9 == 1); +} + +bool aml_has_frac_rate_policy() +{ + static int has_frac_rate_policy = -1; + + if (has_frac_rate_policy == -1) + has_frac_rate_policy = SysfsUtils::Has("/sys/class/amhdmitx/amhdmitx0/frac_rate_policy"); + + return (has_frac_rate_policy == 1); +} + +void aml_set_audio_passthrough(bool passthrough) +{ + SysfsUtils::SetInt("/sys/class/audiodsp/digital_raw", passthrough ? 2:0); +} + +void aml_probe_hdmi_audio() +{ + // Audio {format, channel, freq, cce} + // {1, 7, 7f, 7} + // {7, 5, 1e, 0} + // {2, 5, 7, 0} + // {11, 7, 7e, 1} + // {10, 7, 6, 0} + // {12, 7, 7e, 0} + + int fd = open("/sys/class/amhdmitx/amhdmitx0/edid", O_RDONLY); + if (fd >= 0) + { + char valstr[1024] = {0}; + + read(fd, valstr, sizeof(valstr) - 1); + valstr[strlen(valstr)] = '\0'; + close(fd); + + std::vector probe_str = StringUtils::Split(valstr, "\n"); + + for (std::vector::const_iterator i = probe_str.begin(); i != probe_str.end(); ++i) + { + if (i->find("Audio") == std::string::npos) + { + for (std::vector::const_iterator j = i + 1; j != probe_str.end(); ++j) + { + if (j->find("{1,") != std::string::npos) + printf(" PCM found {1,\n"); + else if (j->find("{2,") != std::string::npos) + printf(" AC3 found {2,\n"); + else if (j->find("{3,") != std::string::npos) + printf(" MPEG1 found {3,\n"); + else if (j->find("{4,") != std::string::npos) + printf(" MP3 found {4,\n"); + else if (j->find("{5,") != std::string::npos) + printf(" MPEG2 found {5,\n"); + else if (j->find("{6,") != std::string::npos) + printf(" AAC found {6,\n"); + else if (j->find("{7,") != std::string::npos) + printf(" DTS found {7,\n"); + else if (j->find("{8,") != std::string::npos) + printf(" ATRAC found {8,\n"); + else if (j->find("{9,") != std::string::npos) + printf(" One_Bit_Audio found {9,\n"); + else if (j->find("{10,") != std::string::npos) + printf(" Dolby found {10,\n"); + else if (j->find("{11,") != std::string::npos) + printf(" DTS_HD found {11,\n"); + else if (j->find("{12,") != std::string::npos) + printf(" MAT found {12,\n"); + else if (j->find("{13,") != std::string::npos) + printf(" ATRAC found {13,\n"); + else if (j->find("{14,") != std::string::npos) + printf(" WMA found {14,\n"); + else + break; + } + break; + } + } + } +} + +int aml_axis_value(AML_DISPLAY_AXIS_PARAM param) +{ + std::string axis; + int value[8]; + + SysfsUtils::GetString("/sys/class/display/axis", axis); + sscanf(axis.c_str(), "%d %d %d %d %d %d %d %d", &value[0], &value[1], &value[2], &value[3], &value[4], &value[5], &value[6], &value[7]); + + return value[param]; +} + +bool aml_mode_to_resolution(const char *mode, RESOLUTION_INFO *res) +{ + if (!res) + return false; + + res->iWidth = 0; + res->iHeight= 0; + + if(!mode) + return false; + + std::string fromMode = mode; + StringUtils::Trim(fromMode); + // strips, for example, 720p* to 720p + // the * indicate the 'native' mode of the display + if (StringUtils::EndsWith(fromMode, "*")) + fromMode.erase(fromMode.size() - 1); + + if (StringUtils::EqualsNoCase(fromMode, "panel")) + { + res->iWidth = aml_axis_value(AML_DISPLAY_AXIS_PARAM_WIDTH); + res->iHeight= aml_axis_value(AML_DISPLAY_AXIS_PARAM_HEIGHT); + res->iScreenWidth = aml_axis_value(AML_DISPLAY_AXIS_PARAM_WIDTH); + res->iScreenHeight= aml_axis_value(AML_DISPLAY_AXIS_PARAM_HEIGHT); + res->fRefreshRate = 60; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else if (StringUtils::EqualsNoCase(fromMode, "4k2ksmpte") || StringUtils::EqualsNoCase(fromMode, "smpte24hz")) + { + res->iWidth = 1920; + res->iHeight= 1080; + res->iScreenWidth = 4096; + res->iScreenHeight= 2160; + res->fRefreshRate = 24; + res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + } + else + { + int width = 0, height = 0, rrate = 60; + char smode = 'p'; + + if (sscanf(fromMode.c_str(), "%dx%dp%dhz", &width, &height, &rrate) == 3) + { + smode = 'p'; + } + else if (sscanf(fromMode.c_str(), "%d%[ip]%dhz", &height, &smode, &rrate) >= 2) + { + switch (height) + { + case 480: + case 576: + width = 720; + break; + case 720: + width = 1280; + break; + case 1080: + width = 1920; + break; + case 2160: + width = 3840; + break; + } + } + else if (sscanf(fromMode.c_str(), "%dcvbs", &height) == 1) + { + width = 720; + smode = 'i'; + rrate = (height == 576) ? 50 : 60; + } + else if (sscanf(fromMode.c_str(), "4k2k%d", &rrate) == 1) + { + width = 3840; + height = 2160; + smode = 'p'; + } + else + { + return false; + } + + res->iWidth = (width < 3840) ? width : 1920; + res->iHeight= (height < 2160) ? height : 1080; + res->iScreenWidth = width; + res->iScreenHeight = height; + res->dwFlags = (smode == 'p') ? D3DPRESENTFLAG_PROGRESSIVE : D3DPRESENTFLAG_INTERLACED; + + switch (rrate) + { + case 23: + case 29: + case 59: + res->fRefreshRate = (float)((rrate + 1)/1.001); + break; + default: + res->fRefreshRate = (float)rrate; + break; + } + } + + res->bFullScreen = true; + res->iSubtitles = (int)(0.965 * res->iHeight); + res->fPixelRatio = 1.0f; + res->strId = fromMode; + res->strMode = StringUtils::Format("{:d}x{:d} @ {:.2f}{} - Full Screen", res->iScreenWidth, res->iScreenHeight, res->fRefreshRate, + res->dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : ""); + + return res->iWidth > 0 && res->iHeight> 0; +} + +bool aml_get_native_resolution(RESOLUTION_INFO *res) +{ + std::string mode; + SysfsUtils::GetString("/sys/class/display/mode", mode); + bool result = aml_mode_to_resolution(mode.c_str(), res); + + if (aml_has_frac_rate_policy()) + { + int fractional_rate = 0; + SysfsUtils::GetInt("/sys/class/amhdmitx/amhdmitx0/frac_rate_policy", fractional_rate); + if (fractional_rate == 1) + res->fRefreshRate /= 1.001; + } + + return result; +} + +bool aml_set_native_resolution(const RESOLUTION_INFO &res, std::string framebuffer_name, const RenderStereoMode stereo_mode) +{ + bool result = false; + + aml_handle_display_stereo_mode(RenderStereoMode::OFF); + result = aml_set_display_resolution(res, framebuffer_name); + + aml_handle_scale(res); + aml_handle_display_stereo_mode(stereo_mode); + + return result; +} + +bool aml_probe_resolutions(std::vector &resolutions) +{ + std::string valstr, vesastr, dcapfile; + dcapfile = CSpecialProtocol::TranslatePath("special://home/userdata/disp_cap"); + + if (SysfsUtils::GetString(dcapfile, valstr) < 0) + { + if (SysfsUtils::GetString("/sys/class/amhdmitx/amhdmitx0/disp_cap", valstr) < 0) + return false; + + if (SysfsUtils::GetString("/sys/class/amhdmitx/amhdmitx0/vesa_cap", vesastr) == 0) + valstr += "\n" + vesastr; + } + std::vector probe_str = StringUtils::Split(valstr, "\n"); + + resolutions.clear(); + RESOLUTION_INFO res; + for (std::vector::const_iterator i = probe_str.begin(); i != probe_str.end(); ++i) + { + if (((StringUtils::StartsWith(i->c_str(), "4k2k")) && (aml_support_h264_4k2k() > AML_NO_H264_4K2K)) || !(StringUtils::StartsWith(i->c_str(), "4k2k"))) + { + if (aml_mode_to_resolution(i->c_str(), &res)) + resolutions.push_back(res); + + if (aml_has_frac_rate_policy()) + { + // Add fractional frame rates: 23.976, 29.97 and 59.94 Hz + switch ((int)res.fRefreshRate) + { + case 24: + case 30: + case 60: + res.fRefreshRate /= 1.001; + res.strMode = StringUtils::Format("{:d}x{:d} @ {:.2f}{} - Full Screen", res.iScreenWidth, res.iScreenHeight, res.fRefreshRate, + res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : ""); + resolutions.push_back(res); + break; + } + } + } + } + return resolutions.size() > 0; +} + +bool aml_set_display_resolution(const RESOLUTION_INFO &res, std::string framebuffer_name) +{ + std::string mode = res.strId.c_str(); + std::string cur_mode; + + SysfsUtils::GetString("/sys/class/display/mode", cur_mode); + + if (aml_has_frac_rate_policy()) + { + if (cur_mode == mode) + SysfsUtils::SetString("/sys/class/display/mode", "null"); + + int fractional_rate = (res.fRefreshRate == floor(res.fRefreshRate)) ? 0 : 1; + SysfsUtils::SetInt("/sys/class/amhdmitx/amhdmitx0/frac_rate_policy", fractional_rate); + } + else if (cur_mode == mode) + { + // Don't set the same mode as current + return true; + } + + SysfsUtils::SetString("/sys/class/display/mode", mode.c_str()); + + aml_set_framebuffer_resolution(res, framebuffer_name); + + return true; +} + +void aml_handle_scale(const RESOLUTION_INFO &res) +{ + if (res.iScreenWidth > res.iWidth && res.iScreenHeight > res.iHeight) + aml_enable_freeScale(res); + else + aml_disable_freeScale(); +} + +void aml_handle_display_stereo_mode(const RenderStereoMode stereo_mode) +{ + static std::string lastHdmiTxConfig = "3doff"; + + std::string command = "3doff"; + switch (stereo_mode) + { + case RenderStereoMode::SPLIT_VERTICAL: + command = "3dlr"; + break; + case RenderStereoMode::SPLIT_HORIZONTAL: + command = "3dtb"; + break; + default: + // nothing - command is already initialised to "3doff" + break; + } + + CLog::Log(LOGDEBUG, "AMLUtils::aml_handle_display_stereo_mode old mode {} new mode {}", lastHdmiTxConfig.c_str(), command.c_str()); + // there is no way to read back current mode from sysfs + // so we track state internal. Because even + // when setting the same mode again - kernel driver + // will initiate a new hdmi handshake which is not + // what we want of course. + // for 3d mode we are called 2 times and need to allow both calls + // to succeed. Because the first call doesn't switch mode (i guessi its + // timing issue between switching the refreshrate and switching to 3d mode + // which needs to occure in the correct order, else switching refresh rate + // might reset 3dmode). + // So we set the 3d mode - if the last command is different from the current + // command - or in case they are the same - we ensure that its not the 3doff + // command that gets repeated here. + if (lastHdmiTxConfig != command || command != "3doff") + { + CLog::Log(LOGDEBUG, "AMLUtils::aml_handle_display_stereo_mode setting new mode"); + lastHdmiTxConfig = command; + SysfsUtils::SetString("/sys/class/amhdmitx/amhdmitx0/config", command); + } + else + { + CLog::Log(LOGDEBUG, "AMLUtils::aml_handle_display_stereo_mode - no change needed"); + } +} + +void aml_enable_freeScale(const RESOLUTION_INFO &res) +{ + char fsaxis_str[256] = {0}; + sprintf(fsaxis_str, "0 0 %d %d", res.iWidth-1, res.iHeight-1); + char waxis_str[256] = {0}; + sprintf(waxis_str, "0 0 %d %d", res.iScreenWidth-1, res.iScreenHeight-1); + + SysfsUtils::SetInt("/sys/class/graphics/fb0/free_scale", 0); + SysfsUtils::SetString("/sys/class/graphics/fb0/free_scale_axis", fsaxis_str); + SysfsUtils::SetString("/sys/class/graphics/fb0/window_axis", waxis_str); + SysfsUtils::SetInt("/sys/class/graphics/fb0/scale_width", res.iWidth); + SysfsUtils::SetInt("/sys/class/graphics/fb0/scale_height", res.iHeight); + SysfsUtils::SetInt("/sys/class/graphics/fb0/free_scale", 0x10001); +} + +void aml_disable_freeScale() +{ + // turn off frame buffer freescale + SysfsUtils::SetInt("/sys/class/graphics/fb0/free_scale", 0); + SysfsUtils::SetInt("/sys/class/graphics/fb1/free_scale", 0); +} + +void aml_set_framebuffer_resolution(const RESOLUTION_INFO &res, std::string framebuffer_name) +{ + aml_set_framebuffer_resolution(res.iWidth, res.iHeight, framebuffer_name); +} + +void aml_set_framebuffer_resolution(int width, int height, std::string framebuffer_name) +{ + int fd0; + std::string framebuffer = "/dev/" + framebuffer_name; + + if ((fd0 = open(framebuffer.c_str(), O_RDWR)) >= 0) + { + struct fb_var_screeninfo vinfo; + if (ioctl(fd0, FBIOGET_VSCREENINFO, &vinfo) == 0) + { + vinfo.xres = width; + vinfo.yres = height; + vinfo.xres_virtual = 1920; + vinfo.yres_virtual = 2160; + vinfo.bits_per_pixel = 32; + vinfo.activate = FB_ACTIVATE_ALL; + ioctl(fd0, FBIOPUT_VSCREENINFO, &vinfo); + } + close(fd0); + } +} + +bool aml_read_reg(const std::string ®, uint32_t ®_val) +{ + std::string path = "/sys/kernel/debug/aml_reg/paddr"; + if (SysfsUtils::Has(path)) + { + if (SysfsUtils::SetString(path, reg) == 0) + { + std::string val; + if (SysfsUtils::GetString(path, val) == 0) + { + CRegExp regexp; + regexp.RegComp("\\[0x(?.+)\\][\\s]+=[\\s]+(?.+)"); + if (regexp.RegFind(val) == 0) + { + std::string match; + if (regexp.GetNamedSubPattern("reg", match)) + { + if (match == reg) + { + if (regexp.GetNamedSubPattern("val", match)) + { + try + { + reg_val = std::stoul(match, 0, 16); + return true; + } + catch (...) {} + } + } + } + } + } + } + } + return false; +} + +bool aml_has_capability_ignore_alpha() +{ + // AML is at least GXBB + uint32_t reg_val; + if (aml_read_reg("c8100220", reg_val)) + { + if ((reg_val >> 24) >= 0x1f) + return true; + } + return false; +} + +bool aml_set_reg_ignore_alpha() +{ + if (aml_has_capability_ignore_alpha()) + { + std::string path = "/sys/kernel/debug/aml_reg/paddr"; + if (SysfsUtils::SetString(path, "d01068b4 0x7fc0") == 0) + return true; + } + return false; +} + +bool aml_unset_reg_ignore_alpha() +{ + if (aml_has_capability_ignore_alpha()) + { + std::string path = "/sys/kernel/debug/aml_reg/paddr"; + if (SysfsUtils::SetString(path, "d01068b4 0x3fc0") == 0) + return true; + } + return false; +} diff --git a/xbmc/utils/AMLUtils.h b/xbmc/utils/AMLUtils.h new file mode 100644 index 00000000000..1387e3d0c04 --- /dev/null +++ b/xbmc/utils/AMLUtils.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "windowing/Resolution.h" + +#include +#include + +enum AML_DEVICE_TYPE +{ + AML_DEVICE_TYPE_UNINIT = -2, + AML_DEVICE_TYPE_UNKNOWN = -1, + AML_DEVICE_TYPE_M1, + AML_DEVICE_TYPE_M3, + AML_DEVICE_TYPE_M6, + AML_DEVICE_TYPE_M8, // S802 + AML_DEVICE_TYPE_M8B, // S805 + AML_DEVICE_TYPE_M8M2 // S812 +}; + +enum AML_DISPLAY_AXIS_PARAM +{ + AML_DISPLAY_AXIS_PARAM_X = 0, + AML_DISPLAY_AXIS_PARAM_Y, + AML_DISPLAY_AXIS_PARAM_WIDTH, + AML_DISPLAY_AXIS_PARAM_HEIGHT +}; + +enum AML_SUPPORT_H264_4K2K +{ + AML_SUPPORT_H264_4K2K_UNINIT = -1, + AML_NO_H264_4K2K, + AML_HAS_H264_4K2K, + AML_HAS_H264_4K2K_SAME_PROFILE +}; + +bool aml_present(); +bool aml_permissions(); +bool aml_support_hevc(); +bool aml_support_hevc_4k2k(); +bool aml_support_hevc_10bit(); +AML_SUPPORT_H264_4K2K aml_support_h264_4k2k(); +bool aml_support_vp9(); +bool aml_has_frac_rate_policy(); +void aml_set_audio_passthrough(bool passthrough); +bool aml_mode_to_resolution(const char *mode, RESOLUTION_INFO *res); +bool aml_get_native_resolution(RESOLUTION_INFO *res); +bool aml_set_native_resolution(const RESOLUTION_INFO &res, std::string framebuffer_name, const RenderStereoMode stereo_mode); +bool aml_probe_resolutions(std::vector &resolutions); +bool aml_set_display_resolution(const RESOLUTION_INFO &res, std::string framebuffer_name); +void aml_handle_scale(const RESOLUTION_INFO &res); +void aml_handle_display_stereo_mode(const RenderStereoMode stereo_mode); +void aml_enable_freeScale(const RESOLUTION_INFO &res); +void aml_disable_freeScale(); +void aml_set_framebuffer_resolution(const RESOLUTION_INFO &res, std::string framebuffer_name); +void aml_set_framebuffer_resolution(int width, int height, std::string framebuffer_name); +bool aml_read_reg(const std::string ®, uint32_t ®_val); +bool aml_has_capability_ignore_alpha(); +bool aml_set_reg_ignore_alpha(); +bool aml_unset_reg_ignore_alpha(); diff --git a/xbmc/utils/BitstreamConverter.cpp b/xbmc/utils/BitstreamConverter.cpp index 74660ea2a9c..f3fbd675227 100644 --- a/xbmc/utils/BitstreamConverter.cpp +++ b/xbmc/utils/BitstreamConverter.cpp @@ -142,7 +142,7 @@ enum * Copyright (C) 2005 Michal Benes * (C) 2008 Wim Taymans * gsth264parse.c - * + * * SPDX-License-Identifier: LGPL-2.1-or-later * See LICENSES/README.md for more information. */ @@ -471,7 +471,7 @@ bool CBitstreamConverter::Open(enum AVCodecID codec, // valid hvcC data (bitstream) always starts with the value 1 (version) if (m_to_annexb) { - /** + /** @todo from Amlogic * It seems the extradata is encoded as hvcC format. * Temporarily, we support configurationVersion==0 until 14496-15 3rd * is finalized. When finalized, configurationVersion will be 1 and we diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index dcdd23b5d8e..cc9575d378d 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -254,6 +254,13 @@ if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_L endif() endif() +if(TARGET ${APP_NAME_LC}::AML) + list(APPEND SOURCES AMLUtils.cpp + ScreenshotAML.cpp) + list(APPEND HEADERS AMLUtils.h + ScreenshotAML.h) +endif() + core_add_library(utils) if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore) diff --git a/xbmc/utils/ScreenshotAML.cpp b/xbmc/utils/ScreenshotAML.cpp new file mode 100644 index 00000000000..8b676733823 --- /dev/null +++ b/xbmc/utils/ScreenshotAML.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/ScreenshotAML.h" +#include +#include +#include +#include +#include + +#include + +// taken from linux/amlogic/amports/amvideocap.h - needs to be synced - no changes expected though +#define AMVIDEOCAP_IOC_MAGIC 'V' +#define AMVIDEOCAP_IOW_SET_WANTFRAME_WIDTH _IOW(AMVIDEOCAP_IOC_MAGIC, 0x02, int) +#define AMVIDEOCAP_IOW_SET_WANTFRAME_HEIGHT _IOW(AMVIDEOCAP_IOC_MAGIC, 0x03, int) +#define AMVIDEOCAP_IOW_SET_CANCEL_CAPTURE _IOW(AMVIDEOCAP_IOC_MAGIC, 0x33, int) + +// capture format already defaults to GE2D_FORMAT_S24_RGB - no need to pull in all the ge2d headers :) + +#define CAPTURE_DEVICEPATH "/dev/amvideocap0" + +//the buffer format is BGRA (4 byte) +void CScreenshotAML::CaptureVideoFrame(unsigned char *buffer, int iWidth, int iHeight, bool bBlendToBuffer) +{ + int captureFd = open(CAPTURE_DEVICEPATH, O_RDWR, 0); + if (captureFd >= 0) + { + int stride = ((iWidth + 31) & ~31) * 3; + int buffSize = stride * iHeight; + int readSize = 0; + // videobuffer should be rgb according to docu - but it is bgr ... + unsigned char *videoBuffer = new unsigned char[buffSize]; + + if (videoBuffer != NULL) + { + // configure destination + ioctl(captureFd, AMVIDEOCAP_IOW_SET_WANTFRAME_WIDTH, stride / 3); + ioctl(captureFd, AMVIDEOCAP_IOW_SET_WANTFRAME_HEIGHT, iHeight); + readSize = pread(captureFd, videoBuffer, buffSize, 0); + } + + close(captureFd); + + if (readSize == buffSize) + { + if (!bBlendToBuffer) + { + memset(buffer, 0xff, buffSize); + } + + for (int y = 0; y < iHeight; ++y) + { + unsigned char *videoPtr = videoBuffer + y * stride; + + for (int x = 0; x < iWidth; ++x, buffer += 4, videoPtr += 3) + { + float alpha = buffer[3] / (float)255; + + if (bBlendToBuffer) + { + //B + buffer[0] = alpha * (float)buffer[0] + (1 - alpha) * (float)videoPtr[0]; + //G + buffer[1] = alpha * (float)buffer[1] + (1 - alpha) * (float)videoPtr[1]; + //R + buffer[2] = alpha * (float)buffer[2] + (1 - alpha) * (float)videoPtr[2]; + //A + buffer[3] = 0xff;// we are solid now + } + else + { + memcpy(buffer, videoPtr, 3); + } + } + } + } + delete [] videoBuffer; + } +} diff --git a/xbmc/utils/ScreenshotAML.h b/xbmc/utils/ScreenshotAML.h new file mode 100644 index 00000000000..26176182a0b --- /dev/null +++ b/xbmc/utils/ScreenshotAML.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class CScreenshotAML +{ + public: + // Captures the current visible video framebuffer and blends it into + // the passed overlay. The buffer format is BGRA (4 byte) + static void CaptureVideoFrame(unsigned char *buffer, int iWidth, int iHeight, bool bBlendToBuffer = true); +}; diff --git a/xbmc/utils/SystemInfo.cpp b/xbmc/utils/SystemInfo.cpp index ed63ae1049e..f2c7a5378db 100644 --- a/xbmc/utils/SystemInfo.cpp +++ b/xbmc/utils/SystemInfo.cpp @@ -55,6 +55,9 @@ using namespace winrt::Windows::System::Profile; #if defined(TARGET_ANDROID) #include #endif +#if defined(HAS_LIBAMCODEC) +#include "utils/AMLUtils.h" +#endif /* Platform identification */ #if defined(TARGET_DARWIN) diff --git a/xbmc/windowing/amlogic/CMakeLists.txt b/xbmc/windowing/amlogic/CMakeLists.txt new file mode 100644 index 00000000000..f6c3e33220e --- /dev/null +++ b/xbmc/windowing/amlogic/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SOURCES WinSystemAmlogic.cpp + VideoSyncAML.cpp) + +set(HEADERS WinSystemAmlogic.h + VideoSyncAML.h) + +if(TARGET ${APP_NAME_LC}::OpenGLES) + list(APPEND SOURCES WinSystemAmlogicGLESContext.cpp) + list(APPEND HEADERS WinSystemAmlogicGLESContext.h) +endif() + +core_add_library(windowing_Amlogic) diff --git a/xbmc/windowing/amlogic/VideoSyncAML.cpp b/xbmc/windowing/amlogic/VideoSyncAML.cpp new file mode 100644 index 00000000000..263e2d092a1 --- /dev/null +++ b/xbmc/windowing/amlogic/VideoSyncAML.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "VideoSyncAML.h" +#include "ServiceBroker.h" +#include "windowing/GraphicContext.h" +#include "cores/VideoPlayer/VideoReferenceClock.h" +#include "utils/TimeUtils.h" +#include "utils/log.h" +#include "threads/Thread.h" +#include "windowing/WinSystem.h" +#include + +#include +#include + +extern CEvent g_aml_sync_event; + +CVideoSyncAML::CVideoSyncAML(CVideoReferenceClock *clock) +: CVideoSync(clock) +, m_abort(false) +{ +} + +CVideoSyncAML::~CVideoSyncAML() +{ +} + +bool CVideoSyncAML::Setup() +{ + m_abort = false; + + CServiceBroker::GetWinSystem()->Register(this); + CLog::Log(LOGDEBUG, "CVideoReferenceClock: setting up AML"); + + return true; +} + +void CVideoSyncAML::Run(CEvent& stopEvent) +{ + // We use the wall clock for timout handling (no AML h/w, startup) + std::chrono::time_point now(std::chrono::system_clock::now()); + unsigned int waittime (3000 / m_fps); + uint64_t numVBlanks (0); + + /* This shouldn't be very busy and timing is important so increase priority */ + CThread::GetCurrentThread()->SetPriority(CThread::GetCurrentThread()->GetPriority() + 1); + + while (!stopEvent.Signaled() && !m_abort) + { + int countVSyncs(1); + if( !g_aml_sync_event.Wait(std::chrono::milliseconds(waittime))) + { + std::chrono::milliseconds elapsed(std::chrono::duration_cast(std::chrono::system_clock::now() - now).count()); + uint64_t curVBlanks = (m_fps * elapsed.count()) / 1000; + int64_t lastVBlankTime((curVBlanks * 1000) / m_fps); + if (elapsed.count() > lastVBlankTime) + { + lastVBlankTime = (++curVBlanks * 1000) / m_fps; + std::this_thread::sleep_for(std::chrono::milliseconds(lastVBlankTime - elapsed.count())); + } + countVSyncs = curVBlanks - numVBlanks; + numVBlanks = curVBlanks; + } + else + ++numVBlanks; + + uint64_t now = CurrentHostCounter(); + + m_refClock->UpdateClock(countVSyncs, now); + } +} + +void CVideoSyncAML::Cleanup() +{ + CLog::Log(LOGDEBUG, "CVideoReferenceClock: cleaning up AML"); + CServiceBroker::GetWinSystem()->Unregister(this); +} + +float CVideoSyncAML::GetFps() +{ + m_fps = CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS(); + CLog::Log(LOGDEBUG, "CVideoReferenceClock: fps: {:.3f}", m_fps); + return m_fps; +} + +void CVideoSyncAML::OnResetDisplay() +{ + m_abort = true; +} diff --git a/xbmc/windowing/amlogic/VideoSyncAML.h b/xbmc/windowing/amlogic/VideoSyncAML.h new file mode 100644 index 00000000000..55b23d9927d --- /dev/null +++ b/xbmc/windowing/amlogic/VideoSyncAML.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "windowing/VideoSync.h" +#include "guilib/DispResource.h" + +class CVideoSyncAML : public CVideoSync, IDispResource +{ +public: + CVideoSyncAML(CVideoReferenceClock *clock); + virtual ~CVideoSyncAML(); + virtual bool Setup()override; + virtual void Run(CEvent& stopEvent)override; + virtual void Cleanup()override; + virtual float GetFps()override; + virtual void OnResetDisplay()override; +private: + volatile bool m_abort; +}; diff --git a/xbmc/windowing/amlogic/WinSystemAmlogic.cpp b/xbmc/windowing/amlogic/WinSystemAmlogic.cpp new file mode 100644 index 00000000000..ce091c57302 --- /dev/null +++ b/xbmc/windowing/amlogic/WinSystemAmlogic.cpp @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WinSystemAmlogic.h" + +#include +#include + +#include "ServiceBroker.h" +#include "cores/RetroPlayer/process/amlogic/RPProcessInfoAmlogic.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h" +#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAmlogic.h" +#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h" +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererAML.h" +#include "windowing/GraphicContext.h" +#include "windowing/Resolution.h" +#include "platform/linux/powermanagement/LinuxPowerSyscall.h" +#include "platform/linux/ScreenshotSurfaceAML.h" +#include "settings/DisplaySettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "guilib/DispResource.h" +#include "utils/AMLUtils.h" +#include "utils/log.h" +#include "utils/SysfsUtils.h" +#include "threads/SingleLock.h" + +#include + +#include + +using namespace KODI; + +CWinSystemAmlogic::CWinSystemAmlogic() : + m_libinput(new CLibInputHandler) +{ + const char *env_framebuffer = getenv("FRAMEBUFFER"); + + // default to framebuffer 0 + m_framebuffer_name = "fb0"; + if (env_framebuffer) + { + std::string framebuffer(env_framebuffer); + std::string::size_type start = framebuffer.find("fb"); + m_framebuffer_name = framebuffer.substr(start); + } + + m_nativeDisplay = EGL_NO_DISPLAY; + m_nativeWindow = static_cast(NULL); + + m_displayWidth = 0; + m_displayHeight = 0; + + m_stereo_mode = RenderStereoMode::OFF; + m_delayDispReset = false; + + aml_permissions(); + aml_disable_freeScale(); + + m_libinput->Start(); +} + +CWinSystemAmlogic::~CWinSystemAmlogic() +{ + if(m_nativeWindow) + { + m_nativeWindow = static_cast(NULL); + } +} + +bool CWinSystemAmlogic::InitWindowSystem() +{ + m_nativeDisplay = EGL_DEFAULT_DISPLAY; + + CDVDVideoCodecAmlogic::Register(); + CLinuxRendererGLES::Register(); + RETRO::CRPProcessInfoAmlogic::Register(); + RETRO::CRPProcessInfoAmlogic::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGLES); + CRendererAML::Register(); + CScreenshotSurfaceAML::Register(); + + aml_set_framebuffer_resolution(1920, 1080, m_framebuffer_name); + + return CWinSystemBase::InitWindowSystem(); +} + +bool CWinSystemAmlogic::DestroyWindowSystem() +{ + return true; +} + +bool CWinSystemAmlogic::CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) +{ + RESOLUTION_INFO current_resolution; + current_resolution.iWidth = current_resolution.iHeight = 0; + const RenderStereoMode stereo_mode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode(); + + m_nWidth = res.iWidth; + m_nHeight = res.iHeight; + m_displayWidth = res.iScreenWidth; + m_displayHeight = res.iScreenHeight; + m_fRefreshRate = res.fRefreshRate; + + if ((m_bWindowCreated && aml_get_native_resolution(¤t_resolution)) && + current_resolution.iWidth == res.iWidth && current_resolution.iHeight == res.iHeight && + current_resolution.iScreenWidth == res.iScreenWidth && current_resolution.iScreenHeight == res.iScreenHeight && + m_bFullScreen == fullScreen && current_resolution.fRefreshRate == res.fRefreshRate && + (current_resolution.dwFlags & D3DPRESENTFLAG_MODEMASK) == (res.dwFlags & D3DPRESENTFLAG_MODEMASK) && + m_stereo_mode == stereo_mode) + { + CLog::Log(LOGDEBUG, "CWinSystemEGL::CreateNewWindow: No need to create a new window"); + return true; + } + + int delay = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt("videoscreen.delayrefreshchange"); + if (delay > 0) + { + m_delayDispReset = true; + m_dispResetTimer.Set(std::chrono::milliseconds(static_cast(delay * 100))); + } + + { + std::unique_lock lock(m_resourceSection); + for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) + { + (*i)->OnLostDisplay(); + } + } + + m_stereo_mode = stereo_mode; + m_bFullScreen = fullScreen; + +#ifdef _FBDEV_WINDOW_H_ + fbdev_window *nativeWindow = new fbdev_window; + nativeWindow->width = res.iWidth; + nativeWindow->height = res.iHeight; + m_nativeWindow = static_cast(nativeWindow); +#endif + + aml_set_native_resolution(res, m_framebuffer_name, stereo_mode); + + if (!m_delayDispReset) + { + std::unique_lock lock(m_resourceSection); + // tell any shared resources + for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) + { + (*i)->OnResetDisplay(); + } + } + + return true; +} + +bool CWinSystemAmlogic::DestroyWindow() +{ + m_nativeWindow = static_cast(NULL); + + return true; +} + +void CWinSystemAmlogic::UpdateResolutions() +{ + CWinSystemBase::UpdateResolutions(); + + RESOLUTION_INFO resDesktop, curDisplay; + std::vector resolutions; + + if (!aml_probe_resolutions(resolutions) || resolutions.empty()) + { + CLog::Log(LOGWARNING, "{}: ProbeResolutions failed.",__FUNCTION__); + } + + /* ProbeResolutions includes already all resolutions. + * Only get desktop resolution so we can replace xbmc's desktop res + */ + if (aml_get_native_resolution(&curDisplay)) + { + resDesktop = curDisplay; + } + + RESOLUTION ResDesktop = RES_INVALID; + RESOLUTION res_index = RES_DESKTOP; + + for (size_t i = 0; i < resolutions.size(); i++) + { + // if this is a new setting, + // create a new empty setting to fill in. + if ((int)CDisplaySettings::GetInstance().ResolutionInfoSize() <= res_index) + { + RESOLUTION_INFO res; + CDisplaySettings::GetInstance().AddResolutionInfo(res); + } + + CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(resolutions[i]); + CDisplaySettings::GetInstance().GetResolutionInfo(res_index) = resolutions[i]; + + CLog::Log(LOGNOTICE, "Found resolution {:d} x {:d} with {:d} x {:d}{} @ {:f} Hz\n", + resolutions[i].iWidth, + resolutions[i].iHeight, + resolutions[i].iScreenWidth, + resolutions[i].iScreenHeight, + resolutions[i].dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "", + resolutions[i].fRefreshRate); + + if(resDesktop.iWidth == resolutions[i].iWidth && + resDesktop.iHeight == resolutions[i].iHeight && + resDesktop.iScreenWidth == resolutions[i].iScreenWidth && + resDesktop.iScreenHeight == resolutions[i].iScreenHeight && + (resDesktop.dwFlags & D3DPRESENTFLAG_MODEMASK) == (resolutions[i].dwFlags & D3DPRESENTFLAG_MODEMASK) && + fabs(resDesktop.fRefreshRate - resolutions[i].fRefreshRate) < FLT_EPSILON) + { + ResDesktop = res_index; + } + + res_index = (RESOLUTION)((int)res_index + 1); + } + + // set RES_DESKTOP + if (ResDesktop != RES_INVALID) + { + CLog::Log(LOGNOTICE, "Found ({:d}x{:d}{}@{:f}) at {:d}, setting to RES_DESKTOP at {:d}", + resDesktop.iWidth, resDesktop.iHeight, + resDesktop.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "", + resDesktop.fRefreshRate, + (int)ResDesktop, (int)RES_DESKTOP); + + CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP) = CDisplaySettings::GetInstance().GetResolutionInfo(ResDesktop); + } +} + +bool CWinSystemAmlogic::Hide() +{ + return false; +} + +bool CWinSystemAmlogic::Show(bool show) +{ + std::string blank_framebuffer = "/sys/class/graphics/" + m_framebuffer_name + "/blank"; + SysfsUtils::SetInt(blank_framebuffer.c_str(), show ? 0 : 1); + return true; +} + +void CWinSystemAmlogic::Register(IDispResource *resource) +{ + std::unique_lock lock(m_resourceSection); + m_resources.push_back(resource); +} + +void CWinSystemAmlogic::Unregister(IDispResource *resource) +{ + std::unique_lock lock(m_resourceSection); + std::vector::iterator i = find(m_resources.begin(), m_resources.end(), resource); + if (i != m_resources.end()) + m_resources.erase(i); +} diff --git a/xbmc/windowing/amlogic/WinSystemAmlogic.h b/xbmc/windowing/amlogic/WinSystemAmlogic.h new file mode 100644 index 00000000000..ab10b5418c4 --- /dev/null +++ b/xbmc/windowing/amlogic/WinSystemAmlogic.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/linux/input/LibInputHandler.h" +#include "rendering/gles/RenderSystemGLES.h" +#include "threads/CriticalSection.h" +#include "windowing/WinSystem.h" +#include "threads/SystemClock.h" +#include "EGL/egl.h" + +class IDispResource; + +class CWinSystemAmlogic : public CWinSystemBase +{ +public: + CWinSystemAmlogic(); + virtual ~CWinSystemAmlogic(); + + bool InitWindowSystem() override; + bool DestroyWindowSystem() override; + + bool CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) override; + + bool DestroyWindow() override; + void UpdateResolutions() override; + + bool Hide() override; + bool Show(bool show = true) override; + virtual void Register(IDispResource *resource); + virtual void Unregister(IDispResource *resource); +protected: + std::string m_framebuffer_name; + EGLDisplay m_nativeDisplay; + EGLNativeWindowType m_nativeWindow; + + int m_displayWidth; + int m_displayHeight; + + RenderStereoMode m_stereo_mode; + + bool m_delayDispReset; + XbmcThreads::EndTime<> m_dispResetTimer; + + CCriticalSection m_resourceSection; + std::vector m_resources; + std::unique_ptr m_libinput; +}; diff --git a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp new file mode 100644 index 00000000000..2b33f846d3a --- /dev/null +++ b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "VideoSyncAML.h" +#include "WinSystemAmlogicGLESContext.h" +#include "utils/log.h" +#include "threads/SingleLock.h" + +std::unique_ptr CWinSystemBase::CreateWinSystem() +{ + std::unique_ptr winSystem(new CWinSystemAmlogicGLESContext()); + return winSystem; +} + +bool CWinSystemAmlogicGLESContext::InitWindowSystem() +{ + if (!CWinSystemAmlogic::InitWindowSystem()) + { + return false; + } + + if (!m_pGLContext.CreateDisplay(m_nativeDisplay)) + { + return false; + } + + if (!m_pGLContext.InitializeDisplay(EGL_OPENGL_ES_API)) + { + return false; + } + + if (!m_pGLContext.ChooseConfig(EGL_OPENGL_ES2_BIT)) + { + return false; + } + + CEGLAttributesVec contextAttribs; + contextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}}); + + if (!m_pGLContext.CreateContext(contextAttribs)) + { + return false; + } + + return true; +} + +bool CWinSystemAmlogicGLESContext::CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) +{ + m_pGLContext.DestroySurface(); + + if (!CWinSystemAmlogic::DestroyWindow()) + { + return false; + } + + if (!CWinSystemAmlogic::CreateNewWindow(name, fullScreen, res)) + { + return false; + } + + if (!m_pGLContext.CreateSurface(m_nativeWindow)) + { + return false; + } + + if (!m_pGLContext.BindContext()) + { + return false; + } + + if (!m_delayDispReset) + { + std::unique_lock lock(m_resourceSection); + // tell any shared resources + for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) + (*i)->OnResetDisplay(); + } + + return true; +} + +bool CWinSystemAmlogicGLESContext::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) +{ + CRenderSystemGLES::ResetRenderSystem(newWidth, newHeight); + return true; +} + +bool CWinSystemAmlogicGLESContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) +{ + CreateNewWindow("", fullScreen, res); + CRenderSystemGLES::ResetRenderSystem(res.iWidth, res.iHeight); + return true; +} + +void CWinSystemAmlogicGLESContext::SetVSyncImpl(bool enable) +{ + if (!m_pGLContext.SetVSync(enable)) + { + CLog::Log(LOGERROR, "{},Could not set egl vsync", __FUNCTION__); + } +} + +void CWinSystemAmlogicGLESContext::PresentRenderImpl(bool rendered) +{ + if (m_delayDispReset && m_dispResetTimer.IsTimePast()) + { + m_delayDispReset = false; + std::unique_lock lock(m_resourceSection); + // tell any shared resources + for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) + (*i)->OnResetDisplay(); + } + if (!rendered) + return; + + // Ignore errors - eglSwapBuffers() sometimes fails during modeswaps on AML, + // there is probably nothing we can do about it + m_pGLContext.TrySwapBuffers(); +} + +EGLDisplay CWinSystemAmlogicGLESContext::GetEGLDisplay() const +{ + return m_pGLContext.GetEGLDisplay(); +} + +EGLSurface CWinSystemAmlogicGLESContext::GetEGLSurface() const +{ + return m_pGLContext.GetEGLSurface(); +} + +EGLContext CWinSystemAmlogicGLESContext::GetEGLContext() const +{ + return m_pGLContext.GetEGLContext(); +} + +EGLConfig CWinSystemAmlogicGLESContext::GetEGLConfig() const +{ + return m_pGLContext.GetEGLConfig(); +} + +std::unique_ptr CWinSystemAmlogicGLESContext::GetVideoSync(CVideoReferenceClock *clock) +{ + std::unique_ptr pVSync(new CVideoSyncAML(clock)); + return pVSync; +} diff --git a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h new file mode 100644 index 00000000000..ed0adb27d68 --- /dev/null +++ b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/EGLUtils.h" +#include "rendering/gles/RenderSystemGLES.h" +#include "utils/GlobalsHandling.h" +#include "WinSystemAmlogic.h" + +class CWinSystemAmlogicGLESContext : public CWinSystemAmlogic, public CRenderSystemGLES +{ +public: + CWinSystemAmlogicGLESContext() = default; + virtual ~CWinSystemAmlogicGLESContext() = default; + + // Implementation of CWinSystemBase via CWinSystemAmlogic + CRenderSystemBase *GetRenderSystem() override { return this; } + bool InitWindowSystem() override; + bool CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) override; + + bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override; + bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; + + virtual std::unique_ptr GetVideoSync(CVideoReferenceClock *clock) override; + + EGLDisplay GetEGLDisplay() const; + EGLSurface GetEGLSurface() const; + EGLContext GetEGLContext() const; + EGLConfig GetEGLConfig() const; +protected: + void SetVSyncImpl(bool enable) override; + void PresentRenderImpl(bool rendered) override; + +private: + CEGLContextUtils m_pGLContext; + +}; From b8e41b2b0f9f1ef7aa471dc05063c8f56345c089 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 1 Jun 2020 18:26:20 +0200 Subject: [PATCH 021/375] Revert "RPProcessInfo: drop method ConfigureRenderSystem" This reverts commit f56348255e5012e3df799b3a2c13e0d765f2c990. --- xbmc/cores/RetroPlayer/process/RPProcessInfo.h | 9 +++++++++ xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/xbmc/cores/RetroPlayer/process/RPProcessInfo.h b/xbmc/cores/RetroPlayer/process/RPProcessInfo.h index 7706c2e1734..75723435e9d 100644 --- a/xbmc/cores/RetroPlayer/process/RPProcessInfo.h +++ b/xbmc/cores/RetroPlayer/process/RPProcessInfo.h @@ -155,6 +155,15 @@ class CRPProcessInfo * unavailable */ virtual HwProcedureAddress GetHwProcedureAddress(const char* symbol) { return nullptr; } + + /*! + * \brief Configure the render system + * + * \param format The pixel format of the video stream, or AV_PIX_FMT_NONE + * if the stream has ended + */ + virtual void ConfigureRenderSystem(AVPixelFormat format) { } + ///} /// @name Player video info diff --git a/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp index 2c795d289eb..5a714e75dea 100644 --- a/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp +++ b/xbmc/cores/RetroPlayer/rendering/RPRenderManager.cpp @@ -62,6 +62,9 @@ void CRPRenderManager::Deinitialize() { CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Deinitializing render manager"); + // Required to reset Amlogic chip to default state + m_processInfo.ConfigureRenderSystem(AV_PIX_FMT_NONE); + // Wait for savestate tasks for (std::future& task : m_savestateThreads) task.wait(); @@ -359,6 +362,8 @@ void CRPRenderManager::FrameMove() if (m_state == RENDER_STATE::CONFIGURING) { + m_processInfo.ConfigureRenderSystem(m_format); + m_state = RENDER_STATE::CONFIGURED; CLog::Log(LOGINFO, "RetroPlayer[RENDER]: Renderer configured on first frame"); From c1da91960591ed847c826998ffa60e4f5fcd3026 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 1 Jun 2020 19:12:30 +0200 Subject: [PATCH 022/375] Partially revert "SysfsUtils: cleanup and improve interface according to the documentation" This partialy reverts commit 98e7dabc8e18cfe2b9f3605f33d7371b194a674f. Leave SysfsUtils only for amlogic. --- xbmc/utils/CMakeLists.txt | 6 +- xbmc/utils/SysfsUtils.cpp | 116 ++++++++++++++++++++++++++++++++++++++ xbmc/utils/SysfsUtils.h | 22 ++++++++ 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 xbmc/utils/SysfsUtils.cpp create mode 100644 xbmc/utils/SysfsUtils.h diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index cc9575d378d..0ad3ad03c11 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -256,9 +256,11 @@ endif() if(TARGET ${APP_NAME_LC}::AML) list(APPEND SOURCES AMLUtils.cpp - ScreenshotAML.cpp) + ScreenshotAML.cpp + SysfsUtils.cpp) list(APPEND HEADERS AMLUtils.h - ScreenshotAML.h) + ScreenshotAML.h + SysfsUtils.h) endif() core_add_library(utils) diff --git a/xbmc/utils/SysfsUtils.cpp b/xbmc/utils/SysfsUtils.cpp new file mode 100644 index 00000000000..29990a64da9 --- /dev/null +++ b/xbmc/utils/SysfsUtils.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SysfsUtils.h" +#include "utils/log.h" +#include "utils/StringUtils.h" + +#include +#include +#include +#include +#include + +int SysfsUtils::SetString(const std::string& path, const std::string& valstr) +{ + int fd = open(path.c_str(), O_RDWR, 0644); + int ret = 0; + if (fd >= 0) + { + if (write(fd, valstr.c_str(), valstr.size()) < 0) + ret = -1; + close(fd); + } + if (ret) + CLog::Log(LOGERROR, "{}: error writing {}",__FUNCTION__, path.c_str()); + + return ret; +} + +int SysfsUtils::GetString(const std::string& path, std::string& valstr) +{ + int len; + char buf[256] = {0}; + + int fd = open(path.c_str(), O_RDONLY); + if (fd >= 0) + { + valstr.clear(); + while ((len = read(fd, buf, 256)) > 0) + valstr.append(buf, len); + close(fd); + + StringUtils::Trim(valstr); + + return 0; + } + + CLog::Log(LOGERROR, "{}: error reading {}",__FUNCTION__, path.c_str()); + valstr = "fail"; + return -1; +} + +int SysfsUtils::SetInt(const std::string& path, const int val) +{ + int fd = open(path.c_str(), O_RDWR, 0644); + int ret = 0; + if (fd >= 0) + { + char bcmd[16]; + sprintf(bcmd, "%d", val); + if (write(fd, bcmd, strlen(bcmd)) < 0) + ret = -1; + close(fd); + } + if (ret) + CLog::Log(LOGERROR, "{}: error writing {}",__FUNCTION__, path.c_str()); + + return ret; +} + +int SysfsUtils::GetInt(const std::string& path, int& val) +{ + int fd = open(path.c_str(), O_RDONLY); + int ret = 0; + if (fd >= 0) + { + char bcmd[16]; + if (read(fd, bcmd, sizeof(bcmd)) < 0) + ret = -1; + else + val = strtol(bcmd, NULL, 16); + + close(fd); + } + if (ret) + CLog::Log(LOGERROR, "{}: error reading {}",__FUNCTION__, path.c_str()); + + return ret; +} + +bool SysfsUtils::Has(const std::string &path) +{ + int fd = open(path.c_str(), O_RDONLY); + if (fd >= 0) + { + close(fd); + return true; + } + return false; +} + +bool SysfsUtils::HasRW(const std::string &path) +{ + int fd = open(path.c_str(), O_RDWR); + if (fd >= 0) + { + close(fd); + return true; + } + return false; +} diff --git a/xbmc/utils/SysfsUtils.h b/xbmc/utils/SysfsUtils.h new file mode 100644 index 00000000000..b173a456bc1 --- /dev/null +++ b/xbmc/utils/SysfsUtils.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include + +class SysfsUtils +{ +public: + static int SetString(const std::string& path, const std::string& valstr); + static int GetString(const std::string& path, std::string& valstr); + static int SetInt(const std::string& path, const int val); + static int GetInt(const std::string& path, int& val); + static bool Has(const std::string& path); + static bool HasRW(const std::string &path); +}; From b26a10a5a4c949248fe90bdf5a64e7ddc2c55ae6 Mon Sep 17 00:00:00 2001 From: Portisch Date: Fri, 30 Oct 2020 22:36:50 +0100 Subject: [PATCH 023/375] windowing/aml: add namespace --- .../windowing/amlogic/WinSystemAmlogicGLESContext.cpp | 2 ++ xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp index 2b33f846d3a..82f7d0d49fc 100644 --- a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp +++ b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp @@ -12,6 +12,8 @@ #include "threads/SingleLock.h" std::unique_ptr CWinSystemBase::CreateWinSystem() +using namespace KODI; +using namespace KODI::WINDOWING::AML; { std::unique_ptr winSystem(new CWinSystemAmlogicGLESContext()); return winSystem; diff --git a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h index ed0adb27d68..faeae8ba118 100644 --- a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h +++ b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h @@ -13,6 +13,13 @@ #include "utils/GlobalsHandling.h" #include "WinSystemAmlogic.h" +namespace KODI +{ +namespace WINDOWING +{ +namespace AML +{ + class CWinSystemAmlogicGLESContext : public CWinSystemAmlogic, public CRenderSystemGLES { public: @@ -43,3 +50,7 @@ class CWinSystemAmlogicGLESContext : public CWinSystemAmlogic, public CRenderSys CEGLContextUtils m_pGLContext; }; + +} +} +} From 25068b58d450632bdb8e4037ab126f06c4f86a32 Mon Sep 17 00:00:00 2001 From: Portisch Date: Fri, 30 Oct 2020 22:37:30 +0100 Subject: [PATCH 024/375] windowing/aml: add registration functions for CWindowSystemFactory --- .../amlogic/WinSystemAmlogicGLESContext.cpp | 12 +++++++++--- xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp index 82f7d0d49fc..914735124af 100644 --- a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp +++ b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.cpp @@ -10,13 +10,19 @@ #include "WinSystemAmlogicGLESContext.h" #include "utils/log.h" #include "threads/SingleLock.h" +#include "windowing/WindowSystemFactory.h" -std::unique_ptr CWinSystemBase::CreateWinSystem() using namespace KODI; using namespace KODI::WINDOWING::AML; + +void CWinSystemAmlogicGLESContext::Register() +{ + KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "aml"); +} + +std::unique_ptr CWinSystemAmlogicGLESContext::CreateWinSystem() { - std::unique_ptr winSystem(new CWinSystemAmlogicGLESContext()); - return winSystem; + return std::make_unique(); } bool CWinSystemAmlogicGLESContext::InitWindowSystem() diff --git a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h index faeae8ba118..cf37ca75414 100644 --- a/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h +++ b/xbmc/windowing/amlogic/WinSystemAmlogicGLESContext.h @@ -26,6 +26,10 @@ class CWinSystemAmlogicGLESContext : public CWinSystemAmlogic, public CRenderSys CWinSystemAmlogicGLESContext() = default; virtual ~CWinSystemAmlogicGLESContext() = default; + using CWinSystemAmlogic::Register; + static void Register(); + static std::unique_ptr CreateWinSystem(); + // Implementation of CWinSystemBase via CWinSystemAmlogic CRenderSystemBase *GetRenderSystem() override { return this; } bool InitWindowSystem() override; From 390b9cbc97933dd9bb475bdc88fb06d82ddd3659 Mon Sep 17 00:00:00 2001 From: Portisch Date: Fri, 30 Oct 2020 22:40:16 +0100 Subject: [PATCH 025/375] CPlatformLinux: add amlogic windowing --- xbmc/platform/linux/PlatformLinux.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xbmc/platform/linux/PlatformLinux.cpp b/xbmc/platform/linux/PlatformLinux.cpp index e01f8216bf0..9aa8f20ce6b 100644 --- a/xbmc/platform/linux/PlatformLinux.cpp +++ b/xbmc/platform/linux/PlatformLinux.cpp @@ -36,6 +36,9 @@ #if defined(HAVE_GBM) #include "windowing/gbm/WinSystemGbmGLESContext.h" #endif +#if defined(HAS_LIBAMCODEC) +#include "windowing/amlogic/WinSystemAmlogicGLESContext.h" +#endif #endif #if defined(HAS_GL) @@ -77,6 +80,9 @@ bool CPlatformLinux::InitStageOne() #if defined(HAVE_GBM) KODI::WINDOWING::GBM::CWinSystemGbmGLESContext::Register(); #endif +#if defined(HAS_LIBAMCODEC) + KODI::WINDOWING::AML::CWinSystemAmlogicGLESContext::Register(); +#endif #endif #if defined(HAS_GL) From ae36e502ae8304836b0a63e92395d2201b30754a Mon Sep 17 00:00:00 2001 From: Portisch Date: Fri, 30 Oct 2020 22:59:13 +0100 Subject: [PATCH 026/375] windowing/aml: utils/EGLUtils: allow generic types --- xbmc/windowing/amlogic/WinSystemAmlogic.cpp | 2 +- xbmc/windowing/amlogic/WinSystemAmlogic.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/windowing/amlogic/WinSystemAmlogic.cpp b/xbmc/windowing/amlogic/WinSystemAmlogic.cpp index ce091c57302..7d4beaf4c69 100644 --- a/xbmc/windowing/amlogic/WinSystemAmlogic.cpp +++ b/xbmc/windowing/amlogic/WinSystemAmlogic.cpp @@ -32,7 +32,7 @@ #include -#include +#include "system_egl.h" using namespace KODI; diff --git a/xbmc/windowing/amlogic/WinSystemAmlogic.h b/xbmc/windowing/amlogic/WinSystemAmlogic.h index ab10b5418c4..1e951df777c 100644 --- a/xbmc/windowing/amlogic/WinSystemAmlogic.h +++ b/xbmc/windowing/amlogic/WinSystemAmlogic.h @@ -13,7 +13,7 @@ #include "threads/CriticalSection.h" #include "windowing/WinSystem.h" #include "threads/SystemClock.h" -#include "EGL/egl.h" +#include "system_egl.h" class IDispResource; From fdc5e20558db6126a2e11a5f3f155eeb795bfcd7 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 2 Jul 2020 18:39:08 +0200 Subject: [PATCH 027/375] Patch: ce rebrand to coreelec, update skin, code remove images --- addons/repository.xbmc.org/addon.xml | 10 +- .../resources/strings.po | 4 +- addons/skin.estuary/colors/classic.xml | 9 + addons/skin.estuary/colors/defaults.xml | 14 +- .../resource.language.en_gb/strings.po | 4 +- .../media/calibrate/cal_ratio.png | Bin 2817 -> 7534 bytes .../media/icons/logo-nocenter.png | Bin 0 -> 3569 bytes .../media/icons/logo-text-withcenter.png | Bin 0 -> 6062 bytes addons/skin.estuary/media/icons/power.png | Bin 1178 -> 2735 bytes .../media/icons/settings/coreelec.png | Bin 0 -> 3573 bytes addons/skin.estuary/xml/Home.xml | 4 +- addons/skin.estuary/xml/Settings.xml | 8 +- .../skin.estuary/xml/SettingsSystemInfo.xml | 101 +--- addons/webinterface.default/addon.xml | 4 +- addons/webinterface.default/favicon.png | Bin 1228 -> 2795 bytes addons/webinterface.default/icon-128.png | Bin 1872 -> 6710 bytes addons/webinterface.default/icon-144.png | Bin 2753 -> 7348 bytes addons/webinterface.default/icon-152.png | Bin 2027 -> 7664 bytes addons/webinterface.default/icon-192.png | Bin 2329 -> 9208 bytes addons/webinterface.default/icon.png | Bin 2753 -> 35786 bytes .../images/thumbnail_default.png | Bin 1176 -> 8696 bytes addons/webinterface.default/index.html | 2 +- .../js/kodi-webinterface.js | 12 +- .../base/fonts/icomoon/fonts/icomoon.svg | 460 ++++++++++++++---- .../base/fonts/icomoon/fonts/icomoon.ttf | Bin 20536 -> 21384 bytes .../base/fonts/icomoon/fonts/icomoon.woff | Bin 20612 -> 12892 bytes .../themes/base/images/logo.png | Bin 805 -> 4303 bytes .../themes/base/images/thumbnail_default.png | Bin 1049 -> 8696 bytes media/banner.png | Bin 12143 -> 20197 bytes media/icon120x120.png | Bin 7581 -> 16686 bytes media/icon16x16.png | Bin 634 -> 3608 bytes media/icon256x256.png | Bin 23461 -> 23832 bytes media/icon32x32.png | Bin 1483 -> 5272 bytes media/icon48x48.png | Bin 1737 -> 6962 bytes media/icon80x80.png | Bin 1345 -> 7793 bytes media/vendor_icon.png | Bin 2247 -> 6046 bytes media/vendor_logo.png | Bin 9541 -> 13071 bytes privacy-policy.txt | 57 +-- system/peripherals.xml | 2 +- version.txt | 2 +- xbmc/network/upnp/UPnP.cpp | 20 +- xbmc/windows/GUIWindowSystemInfo.cpp | 2 + 42 files changed, 427 insertions(+), 288 deletions(-) create mode 100644 addons/skin.estuary/colors/classic.xml create mode 100644 addons/skin.estuary/media/icons/logo-nocenter.png create mode 100644 addons/skin.estuary/media/icons/logo-text-withcenter.png create mode 100644 addons/skin.estuary/media/icons/settings/coreelec.png diff --git a/addons/repository.xbmc.org/addon.xml b/addons/repository.xbmc.org/addon.xml index 2a595c681d9..96ef8533444 100644 --- a/addons/repository.xbmc.org/addon.xml +++ b/addons/repository.xbmc.org/addon.xml @@ -1,7 +1,7 @@ @@ -9,10 +9,10 @@ - https://mirrors.kodi.tv/addons/piers/addons.xml.gz - https://mirrors.kodi.tv/addons/piers/addons.xml.gz?sha256 - https://mirrors.kodi.tv/addons/piers - https://mirrors.kodi.tv/addons/piers + https://addons.coreelec.org/piers/addons.xml.gz + https://addons.coreelec.org/piers/addons.xml.gz?sha256 + https://addons.coreelec.org/piers + https://addons.coreelec.org/piers sha256 diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 46166dd2d8e..c223967a10f 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -626,7 +626,7 @@ msgstr "" #: addons/skin.estuary/xml/SettingsSystemInfo.xml msgctxt "#144" -msgid "Build:" +msgid "Build" msgstr "" msgctxt "#145" @@ -761,7 +761,7 @@ msgstr "" #: addons/skin.estuary/xml/SettingsSystemInfo.xml msgctxt "#174" -msgid "Compiled:" +msgid "Compiled" msgstr "" #: xbmc/dialogs/GUIDialogMediaFilter.cpp diff --git a/addons/skin.estuary/colors/classic.xml b/addons/skin.estuary/colors/classic.xml new file mode 100644 index 00000000000..dcee11cc8cf --- /dev/null +++ b/addons/skin.estuary/colors/classic.xml @@ -0,0 +1,9 @@ + + + FF0E597E + 330BAA8E + FF1A2123 + FF12A0C7 + 8012A0C7 + FF11E7B1 + diff --git a/addons/skin.estuary/colors/defaults.xml b/addons/skin.estuary/colors/defaults.xml index 24648aa8603..1375c6bf133 100644 --- a/addons/skin.estuary/colors/defaults.xml +++ b/addons/skin.estuary/colors/defaults.xml @@ -1,9 +1,9 @@ - FF0E597E - 330BAA8E - FF1A2123 - FF147995 + FF6B1414 + 33DB2C83 + FF262020 + FF952F2F FF000000 FF909090 30FFFFFF @@ -12,11 +12,11 @@ FFA0A0A0 FF7ACAFE FFCE4421 - FF12A0C7 - 8012A0C7 + FFC40300 + 80C40300 22000000 60FFFFFF 40FFFFFF - FFFFB70F + FF24C6C9 FFFF0000 diff --git a/addons/skin.estuary/language/resource.language.en_gb/strings.po b/addons/skin.estuary/language/resource.language.en_gb/strings.po index a98ad2b03f8..4cdde35a06a 100644 --- a/addons/skin.estuary/language/resource.language.en_gb/strings.po +++ b/addons/skin.estuary/language/resource.language.en_gb/strings.po @@ -470,7 +470,7 @@ msgstr "" #: /xml/LoginScreen.xml msgctxt "#31098" -msgid "Select your Kodi user profile[CR]to login and continue" +msgid "Select your CoreELEC user profile[CR]to login and continue" msgstr "" #. viewtype name @@ -581,7 +581,7 @@ msgstr "" #: /xml/Home.xml msgctxt "#31119" -msgid "You do not have any add-ons installed yet. Visit our add-on browser to browse through our collection and improve your Kodi experience." +msgid "You do not have any add-ons installed yet. Visit our add-on browser to browse through our collection and improve your CoreELEC experience." msgstr "" #: /xml/Home.xml diff --git a/addons/skin.estuary/media/calibrate/cal_ratio.png b/addons/skin.estuary/media/calibrate/cal_ratio.png index a8ee5a32c833b1968af1533aaddfff530aee9233..ef21a1c24b3545d45635550dd68f637870fb2b09 100644 GIT binary patch literal 7534 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 O)KJe=|ybPxh2A_PJ#ltBelnvv#R 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 Oqi$|&jlFB=9`j!mRd=EQ literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/media/icons/power.png b/addons/skin.estuary/media/icons/power.png index bd8f092458a8428c820643ca6f19999064f965e6..41ce30d26c41af9f5593ad32df95516dfd266b17 100644 GIT binary patch literal 2735 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 F`yZ*!Fp2;G literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/xml/Home.xml b/addons/skin.estuary/xml/Home.xml index 25da58c2f53..0fdcf2df2f7 100644 --- a/addons/skin.estuary/xml/Home.xml +++ b/addons/skin.estuary/xml/Home.xml @@ -1168,8 +1168,8 @@ conditional WindowOpen WindowClose - 30 - 90 + 22 + 95 4 0 diff --git a/addons/skin.estuary/xml/Settings.xml b/addons/skin.estuary/xml/Settings.xml index 8d51703075c..a074d89bb26 100644 --- a/addons/skin.estuary/xml/Settings.xml +++ b/addons/skin.estuary/xml/Settings.xml @@ -114,10 +114,10 @@ icons/settings/system.png - - RunAddon(service.libreelec.settings) - icons/settings/libreelec.png - System.AddonIsEnabled(service.libreelec.settings) + + RunAddon(service.coreelec.settings) + icons/settings/coreelec.png + System.AddonIsEnabled(service.coreelec.settings) diff --git a/addons/skin.estuary/xml/SettingsSystemInfo.xml b/addons/skin.estuary/xml/SettingsSystemInfo.xml index d1b17323e0b..058cf7979e5 100644 --- a/addons/skin.estuary/xml/SettingsSystemInfo.xml +++ b/addons/skin.estuary/xml/SettingsSystemInfo.xml @@ -214,14 +214,6 @@ 420 - - Button "Thank you!" - - - - 420 - - @@ -245,7 +237,7 @@ vertical Memory Text - auto + 1420 80 top @@ -255,13 +247,13 @@ Memory BAR - 730 + 1420 16 system.memory(used) CPU Text - auto + 1420 40 center @@ -271,7 +263,7 @@ CPU BAR - 730 + 1420 16 System.CPUUsage System.SupportsCPUUsage @@ -279,91 +271,6 @@ Conditional - - 155 - 420 - auto - 50 - bottom - 10 - - black - font25_title - - - Kodi build version - 10 - 200 - 420 - 820 - horizontal - - Build label - auto - 40 - font12 - - black - - - Kodi Build Version - auto - 40 - font12 - - button_focus - black - - - - Git version - 10 - 235 - 420 - 820 - horizontal - - Git label - auto - 40 - font12 - - black - - - Git version - auto - 40 - font12 - - button_focus - black - - - - Build date - 10 - 270 - 420 - 820 - horizontal - - kodi Compiled Text - auto - 40 - font12 - - black - - - Kodi Build Date - auto - 40 - font12 - button_focus - black - - diff --git a/addons/webinterface.default/addon.xml b/addons/webinterface.default/addon.xml index cef2274b676..7abab526c9f 100644 --- a/addons/webinterface.default/addon.xml +++ b/addons/webinterface.default/addon.xml @@ -2,7 +2,7 @@ @@ -59,7 +59,7 @@ Procházejte a interagujte přes prohlížeč se svou hudbou, filmy, televizními pořady a ostatním. Streamujte hudbu a videa do vašeho prohlížeče. Upravujte a spravujte vaši multimediální knihovnu Kodi. Gennemse og interager med din musik, film, tv-serier og mere via en webbrowser. Stream musik og videoer i din browser. Rediger og administrer dit Kodi-mediebibliotek. Musik, Filme, Serien und mehr mit dem Webbrowser durchsuchen und steuern, Musik und Videos im Browser streamen. Die Kodi Medienbibliothek ändern und verwalten. - Browse and interact with your Music, Movies, TV Shows and more via a web browser. Stream music and videos on your browser. Edit and manage your Kodi media library. + Browse and interact with your Music, Movies, TV Shows and more via a web browser. Stream music and videos on your browser. Edit and manage your CoreELEC media library. Navega e interactúa con tu Música, Películas, Series de TV y mas por medio de un navegador web. Reproduce música y vídeo desde el navegador. Edita y gestiona tu colección de contenidos de Kodi. Navega e interactúa con tu Música, Películas, Series de TV y mas por medio de un navegador web. Reproduce música y vídeo desde el navegador. Edita y gestiona tu colección de contenidos de Kodi. Sirvi oma meediakogu veebibrauseri kaudu. Voogesita muusikat ja videoid oma brauseris. Muuda ja halda oma Kodi meediakogu. diff --git a/addons/webinterface.default/favicon.png b/addons/webinterface.default/favicon.png index 39aea127a7b55f4a950d270d99bc7015977d6010..bd2600eae51e2804fec7736a1a6dee4999dbb187 100644 GIT binary patch literal 2795 zcmcguc~}!?8Xpf7aV_i$0=DW9yd{~LoXjXF2|=O;xhg8HIGLG*EJ-G2MoF+7DlWo$ zJt+3jNTrEneX_$@ArFu z-}`>wj86Ul`XCY8YoT!hIbQXCJ0#V8hxpuq?x5n^&eiV|Wm-1`%N)oiq$hz*bE zT?_O=1j#&aB@iSvHC2=<5wSJ{g5fxhpkhQU76OEjb69xNF0^ogeICLYj}y1DO}nJ95-J-*@~kbMWlep%9Np&yR>jHT$q7aa&8BiwobmMHnQ)S4LIhyP zaUrS@qVjl5LZA`?3!a9G2^8&tYFXN-cl;VE7I#Bs1Sb9!6r>wX^5mOm3Td4b69$965D!+6v|fTM^e?^5w;4gzk)~I@I$6=c zBdU-|f>jZ6rCOrEFtrp{hJ}Tx#h6^GQs9^>OaRjqp=WJo5~PUHOd1%(YB31lmrPWw ziM0VKf!>l=$2BTdw2jppO<;nHovnm5YLy(r<#M4|ggMIvP;iFI*o-R}I>N@9;qEjh zjQxn$OH=ZCnmx8aCYNx(2~ql5Tf{{s(#U~abM&4Dam=#bmdOaXfhr+L%30YUAQEsE z^bYn%7?;R&QbvLaDG7=Rr3@+&k`kN}VkA!MaH(7&k&=D(smTm**_W*SWMI$Q9INM3 zNgESp0GZKGEvTF)91c&`l9bWnEHh9MJ(g55X)!S1xJ3lcSe@t6{~pns z2=`?kbEfW&Y!N*5zXIuAZO^sK2R@kq@t z*ZfA0zPv4B&#vvK^)2y1#|u8nJ%as3+Is3OQ{CBxzU`0p9X>Jj@&h+mp7?DWv`u$1 zdG#Im)`_@)Z-#92m}M$Ib0=oT>ScT8eXsfM_P;k9YS)hjgBOp#eXPUp4+%d`5Atx7 ze?2Mdy6Mw3p39Y79rV77rf-; z7n==DsXrI4yYSukgVCnH27|#Z_eEB2b_!V_#yw3=EMdM zM`z+BXwCi%j~iC?zWGzL>7bpbybrF!w9~Wvl2#up*QZUm+h|6M^w;Ar#l3emYOGQ` zF`HiDu1PW${<+ec-sP~_AIGpN*gOmMrNt)3rd?f<<2^B+u*8A z8~oe@@ZXP|*7En~i`82){FUPBAv(6Mc2&-7$lcLNojdYEx$~xfyJ_XeRjaP~#Cc6@ z3H4QW?fBEPfbsi2T$XyL`FY`n3k#B}2YyQXAD;H?=VKGjbo?Z$Iq#D=G_JHlw{i(q zc0)XBxaaWCOh?a7D}J&8d)#_&998k|C99+BVUbrp7jU<+*#1>HZ7KG6Pk-!SVw=u0 z6Dt~&H};R`H~6HhE~M)b=xR)r{0zVU!2MnAi`9kiU#k70OWWdhyCJW}ytTe1H$ShY z@}8p9H*3)B%^mx79Ro`a>@Lh4tT|9~)o)8mMrOyrwnggNuXhYH*H=6!**kZ=zpQpl zHDrq2@uRo&(UbCkGkoBJJN|VaL&`|~KQ^}Chi@G`U0F3MbME{(qm*T*tqHAXA7e$u zE0k|5ZKbR8v{RA}%+}UpN%M%P zlba1w?%f_%mzoh&7EyXXFkez}zIjnzW670T2mTn^n;w#sHQf93)1#L^xN_}j?Etq6 WYj;;yotf$U9M!0!!VfE#uKo`)#uCB+ delta 497 zcmaDYdWLg?q$V>11B1(wu46!oCEd~2k%3`jKlh(RRv@3TILO_JVcj{ImkbQdN)xU6 zRE>3$4Gawx%*^x*O-#+q40RNY3=EC*4S>i@*U-eu)Xd7reB)b2#>st5hLfF`#W@Vj zO^q#_-ApIXW07L7$Sv^owQ`xfpG9i&H)g5H=`0f_Uu3hbU)<|Z3$)KWz$e7jC$ms& z<8!G^FGM#!2Qu&@5hMjVJ6-}6+wXr3(IT+^Ia7c1M4++aB|(0{47Pj~?9Dyr1=k;$ ze?W5TUfs|;xku-#{`+J7n4zA5;hd+7V~EA+*2@>gToeQv9tvyyP70GgwBV^;*J5s_>OhmmYXSEgTOBLzH_rQg`ox2M#tnA!_vN2s z_@Z~;TUv{4W%};B-_JK#cUs6ZRk|0RQEx1^XE`H&@OX@yl-pAyhPmfe_Bl0nq$Rv8 zw5wY3?y+)v!rYAeo)H!%>ij0tN;K2 diff --git a/addons/webinterface.default/icon-128.png b/addons/webinterface.default/icon-128.png index 85def0fd2567c566bde747c0e4b2c94cc30dac29..032b4a79fce409d7cfc157e25de2d3577b9d3244 100644 GIT binary patch literal 6710 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 delta 1325 zcmZ`&drVVz6#n(aN+H;mha;>voJ2CGJNw#cQZA1N!SE^` zKLUn%!B7~N7Z%EO8XUso3nE}aSmcSxa-|z_g>pL~UztmS+|ytvU|T^^Zt*@@pb{dn zvf{$xc&lNtI7Si<^LP@zD1y(IL~_LvJ{$*&!^CW;qOc?}Q4m`!-CLMfRK6j0E!$aH zm0OT2c*Bidh^s6jbGN@45-l{uhGgZN@^%Vx7d{ZPAU9Mw;)0+bS1&1aO0*^=VY4Km zpw#Dz-f7AYcjok{?Sgw*b_C8o6$R1Ej`D0fgs+Y zS>x?;?2F*G-TlOyBVLq%83{P9_NAF9pjGIRGI1NU3u!5aYS1He$uZwP8uzUCu+T*} zg=ZhUkpXXf?@~8QGn35d?y70IgCW`M3SquBgGzl>+GmJR5;0*=`W`ZvkSgxR0gaIT zFPN&K)S|}$)x5Otqh;W{I$QM{6;l~KgJB{xHLbXN05q4&lT>TbYV4x=LsgVRirrIh zRN>JGHf~QXIgLVCsQF-DshtSa6UCIe`Q&$B8_3KeEGOb_!SpYu5_!1+LAM~;bHaLsuLSKWrk!M;s0&)>3vDZ+#&gS`1 zk^mnCl&;nbe&mCiB{+%@hX=}vTm1(g%~>h*!P2H%p3ezShjr8XHWbUMzv|5b=e_9- zmf|5Qf<*l>M*Q<^|5ZTZYqRhb=dig)WgG-VTX1s_v%%Oi!vI*Xt$96SH@(Cm_w-2z znMjj2RZ>Knoeo#G0BeSw@d@a*g$6)lnJ<0FpvD%t9D1JliaAH9vqeMDOs3hZ{Cgn; zSu^c)l>e|Y1!**fSQyo4kIKW8N)lvFo=4Xd`!C{N+LK im;q*BGWxf7IYTBUlEx`s!Qfx;|j^ZCROFV`E diff --git a/addons/webinterface.default/icon-144.png b/addons/webinterface.default/icon-144.png index 5da9014a3ab872bd744b2f81ecb57722968382ba..21528a6394e59924f6c0ba72f3fae8ccf97a0ae7 100644 GIT binary patch literal 7348 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 delta 1482 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 LPa2dRcZ>O7f@0*Q1 z|K{n%o-_c!F1UtgPV@|H9R@)2Dc1Z>#EhZ*#MlUMz1L|R0NVTIA?0s#_ySZA?CZ}H z2)F`oG=SsJ^Wy~i1#)}>0z{xc{X39;vMFI+Oks0Mi;yF8`3xtFZm4#iqgxsuGyz|veK4^da zJJRpqr0z6U*WFLBd^CXj?0H=Q3G^bX7QY~gQlQ7#GO|qKGKRAY40|Qn;i+xo`+w1} zaGSp8;plOm=(ok8Omls-M%d)O0e}23sBZYw))!<;QkD;g5Sk8*KsSboRVHgiiBXwu z$9W(ke~Nc5(&3Nuh%MohO?H1C4WR3;kh=7H`+C}QmT$1pJs}xT{N)_eduQO4yIhtv z6T1fdNsyrG^1Mr`x+dY32VK5q;MyGLR!iOV5t(a;;^5CVME(qPTxaa`R{^D}%lj@l z(bWiS{Khhv+nyCaycU|Cx5ls){yjN*!77QjCWEmJcSx!OXBVa05>bVwHnn`Tz_r7a znET|z82h1wDmTYjO-EK^{<{yt!`AjYCJ)>R{~odr`pK&kKu=BAxAo!3J|$+V|*)PJyaY?Sdd0V`hm ztIKKeE%0(dS5sOTE~Jv6XP#%&k951zOmd%S$)RJri!I4=viEyfKb3s;#GpWHH8Pbv zd$Dpxk0b{vV^=9PqMj zJEZI}ENVgtnU4tTwsn88uq^oK{JrkAQ@5Sn=sR~SGA9ZV>cDrC;`$V^BChUpk*oQ& z*n7rDncO>NrBkkq?OOOUOLk}KoL8T|H?>;$WW_m8xK?|@nZ4)8=zkF`g!uu&Le3)v2 zk5f4QS=Nkc`6^L&?##`qrTf=-W_FuBEaMU12yvfbsaCH9XUql(R!7H2U5`kS{sqdb B)N%j- diff --git a/addons/webinterface.default/icon.png b/addons/webinterface.default/icon.png index d2a6a57b0dcb16c9a4eb7988b946178c262392c4..a9ca1d05c26d7d4517f1e0c152de816eaf3d4cbe 100644 GIT binary patch literal 35786 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 delta 2190 zcma)-Yfuwe8ir4I5(I*Pi2@=HEh6YDkbs7ZV!}<42?GkVBGU{M+Rp*@g z>N%a2aNCmdrE6#afc-b)VoeKuZ%||q0L^7g!)}IY59RLI8Vnk{owNYZT+RfXUghJF z%HeVm9?z4zeglukU59vcxZYkIQ}H~w>jO6M0=#|Cj8#?KhBy^2uy4gj7_o}sA**-p z%MwfSbJtWTXiiCCCX(mt?-v~A!{c(p*8B1NgZw#MUa(&Xha2j<#RVzI%#Mr<2$iHB z%-oZew=Fc##nkZ>?-K|7&ia3mtvF}th5Vl4eJNF;wRHMl$oGq(9A+?jwPUp3^b{V^ z!q_n3z8t&e;s3J|&SXuRK-YQ74R%6WAHod z%otJC;YN(j<_<^}wi@cjw2r-Z3F=XA2eFK;)=DBoN^FI!m`tNv6U!F)X(hp$77T*6 zqBC$e*~!?1->{q1DdJSOv3O{Lj9|d{jm73VMKscc1;{jHC*1|V!8)N+py9|Z%uzN% zM$lRK4cl;?LVz@;>h-N`|%R#?$0#S#8lKK?!Lt zdt78MND-q=svN!}5`)&Lew$xwha{loD($@2Vk8xns`T?dEMz~Xl%^V%nU~)mj8SbM zPt%^hpID_qov2bM@x{anO)@Hl?_C{SqiH4~diHiKM3r$Lc#iUyOlqHiL^bLnDoF&D zvy!d*{S5dbuybyL`EHX^ewCIuh`DR}<<`-`l*BY^VyQPi$S@EwP=ppWmeQx);l-~h z?~@L%Qh0$NI?SRp9tc%-nyDrYIdo3Z$wj z%J(oOuT-72K=Q~BZ41;S@#ARj3mT%Ths zhWJz=;$U6wNA35SSzvw~a_!;w3ymZk1f+UNb%XOv+@I-lF?8`qAqo3-te(OCcVwd5_QT?WmYLbfVPC2et>GeD7r8Rap9TDlM!7Eyj z%}iTpr8-J3bwi#q9-7fLx#TieM9p|?25a)k z<=ADQ%@?|d)Zo_4n|m$Vt)4eO$8%UM`eKpMF=RHGGY zTVh{1yS5G+7{v z$5Ru=Kk#f diff --git a/addons/webinterface.default/images/thumbnail_default.png b/addons/webinterface.default/images/thumbnail_default.png index 463902f32e889b847cc846b03b6e98df2c48053e..f43d51519cbd648318d2b8df7cbdd07f348e8d9c 100644 GIT binary patch literal 8696 zcmd6MXH=8T7H$whDS}dLAOr}V5R%YB521zLi+~V9=nzVzqbMDb5}F_&2uPDEA|O>z znpCAXX)0Ysiqspv&-0yg?mBJVU-w<>U2FEto@YP1%>FSGrK_!af%-Z%006jvR#Vak z0LW}l|MTRe8Q|MW29i*qt7)iA5>HM}*w|Qa++bm6zro1~MV0)7;$5%*@2p)Y!zt2o8tAU{EMjL_`Dvfd~r=gTY`SAt6CQK>>MrIXO95Sy>qw z8EI*0DJdxg0wF0WDIp;tE-o%6CMGH>3O6z`G&D3YFwoc6*VEI})z#I}(b3k{*3#0_ z)YR0_&`?)bN2AedYHF&gswyff%F4=0N=hgcN>Nb}i9{+WD9GQsWoc*`L@ z1^{H_ecdrwXB+`&gR^&ZljYoe-OLGew3X#F5z`XUa!27D9M$|iaR&a{hFE`REW(yk zUJfYZD@6iu#St(-Uso45FDYMH&OdOaNaNFK2q*9l6@s%YC-T%F&{Rtoh{Ai~fMUYp zU@TNr3@CvRhKj>Q5eOk5seUCPB9ag&91Ing5*3kx!GOO%PLefGTRSOzC6(W{NRlk4 z1A*W!1%deZ_z3&Jh4G&D5GbjJAR;gb3`kJ{b?S|~6v`8aA>cg?@pzZNB}(^ilz}MJX<`sSJ}nH^(e2a^ z{(snoQ^F8%vYe+HU=azhh`1pXE+qn&f=UXCz@$V({#4b%+dA6${j(|zZYUxt1xJvw z{7K!j6u%QZ`sSDBKny z3YLINh=WD#Y!G0Cq&O4|#b9hn4a?33F7~Ipr2R^G?CsM!INkr}k+8*MNf>{pCm}8= z3WZ@1U|Sok1Q;VKY70h4*h+wHFye3#n24Q?4Fd5uHaeb;q*IA;`A5`KR<F}?nOcMTT>o_+Ov?r-)2Yn$10szG^iPb%1)8c#UJmfMdd8I8rm`|DjGIg< zUM0p()I?`jbJk?|&ea=Ui$+Fi5OdDEPk8E!VP1E(SC)EGHyf*u>1YkPo(2GBDX$VQ zS*xi8b6g_}w>|ss1=A1ps^7rD3I9pwpLR=j+>ZNX4&drKF9PCfiH`xGU}tFmli)5i z&9!&AMeV<+{T(gl3LTlspX_r302bBh0p_KZCF@kLH~ayBi)4Zued9YHFW3uvOQ=O{ z$JMJUgqg6j*UOB28}m_qN^@ZR87qmRj-=zRlam^&D4yRn|DIpQa-)avrHH0n4rc0s zX`4m8WP3zNA!>Li=Te8!an;EhZusOdt<4$BYa&XD@?#A?WQu01jIhOR*XK+);4)>s z@ft&spd2iyPHezDM3`UeLTkIH-EhiK3KJn56hJ;0RhHgP8NskHh>W$oAE6Db8o~oc`kch z+Q*>0W_Wl0u=x3}fS2%zdT(CUC+{9To+4j&vMdpGPPCOcVm!;=L~+pjRK;;b*>j(H zks@SkmD4T4JUe2svww9jQ#h#bUF003-vmK7AdQsk1BTfcb>oH{$V9K0=-}~y$7(P0xl71}0 z8r+4n%(^DtfJ}Z2Gta(7Y?KWuetkZMU_*}GHxTaHr~r8&JYe!>LVfSwGd)QBmB_m& znjT2Dz-1u&y$&@V56&=c&~@+LwyYFb_w^ABuwMKS-}@zXShbapr|xJfFtUI>sNB%i zleW~jT5$XDi6X)CM{|OY=K4t`hbnPNno@)x<-vGabCtk>z_T&u5fZQMUn?V1eNGyr!6WI8D zl@xK>{>aOv-!;^muNoF`^k6G%*J$Eu_hqh)c)Vrhm(^d!zOQA6EIc-MS+IK5J=7-L zBlI7uS05f&egR(*{86v-?8uG#^XK8p=iSp1OItK%0{fk}=X3S>pM?j>4zf4$cmXD} z9n2d0**gYDstjYvva6=l_xypm!t*K*LA-(7ciU-b5ELPMLz7Rov7*npZFvk`!Wwmu zxoN07w9|(}_g$bfp)ZYH5Bw&v%F4_10%{wNcc5cm--;QXIjcL#NB02oYBJ=u7~VXr zKJq|hMqJ(LxZJ$(mPV8gy_WK?=hSLh8<(4go3c}E0G&(qbms-j@0AG(yaQTU$_O^i z-0a1iwR?44Yc43nRPSwUIf1dvN(Nf*&z1C8@v1Y;JWVcnBQ+&3bMc%~0W{!K)ml^0 zp0&b9vB4dCiwDS>`fvLb_FMT=CY}LyHZ{hKG1lrk&(WKfH6VFj2eZWqiZ0m~dJYdf zN4aNlJd^dc>6hzWQ907#F&S5wS<vl=#2?Nj&#)dk1h9Nl@aJUN(E z9Iu36p1x7itIzdhFCN4E>;swo=^G{la05$$o;PgM|~T;xC^3I z5bx@FU*gPu&P5z;`uw2ep6|H=rI9=2u%N3|=v(&V#t*!Zk8f}sT^4B{NH=GwEit#5 zC?-Irq>hNwv{NjDur;bN(>#s}zH8D<6}R;xexy$*g>7&(nVz2#_|6DSZ^-Jo^6icC zJzO4QA0=hU6f-zX^^ou21P%7Dmy6egEgt* zn~w~^JC|@k`JOvvcx%atb)~x+pTwQQ;j>{ zkD587Pq!Y18u05G9hc32w%L%5Rtu^^r?*7r(Oy+@KMz^&0gGIg1y%=s*|;zrD^rD*XYQqYrpvRyp?80x97YfQwkT~3@b!W)+YV~j< z0abOp-!-!*e2xVWxn4O%_FWd4R99*;r$V#cUF4RXX^W>%W3LE03SpXNnC`EL*fP6S zGDv>sokEy5k(Zy9+}-}9G@IK)c^7`T+5BWl%={aiQh<;sgM!#?_Gsn{xE~w~ZRec# zfjgBmHCNdY7YI7(tAa0Ln_higpQ@d>KmGEYw_>;5lUqbMZRV@_67KK^zBZ7f3BOyf zIr>w^Lyd{kSKIFYd{qdB7T>-wc*TM_i%%NXmpZ5p(|GqeP=&4{Wq`_GiPt0v#~S=J zaq%h^I`DH<+(AUq?kUxZs#RT|qlqZfBZ?lYnP-|xR`y9Xb+yyqGC8~Ehh5*@J75SY zKe(&>n&ArqFC0c6^J3e`CnI~a?q;gDkZ=`fHK(HY1Z%nWloztpEo!CL^SO3&fnw)2 z*;j)rRQm#$C5wy8Q5IO2@ZACLHKCaaDYc`R8v`m;8n5DcKZhJyjYtKBRBzf>3ultw0dTRNp zw`%r+%%jHxN)h(&es1b`D}7 zBZYPd28;uP42p|lI@R?F{0E>8e)i~|!Fmk&o0sv2=YQF|1&50mchziihJhb!dAB`V zmRz9*=pm_6El%J^mmd5KRc1e!TtvmF@?CPLSu-s5S0fVCU~io-JV>@|R8~;- zy+xt#qg8q5yUl^+YW{reas^ujZyqW~fEe#K3e=@Cq)Bw^CoXW=Xm)Y7@FG-Vg4t7V z0b?5~{1O7;yc@#Sj>qOLylFxj1^K&)T>?(I28U_-(UT3yCS&JZlTGQ#q zNNSUzCu*Xvena!5qgbd{8i}%xn4H)2yv^sLeV-;~_}=?j5>3p+J^g+F`}J93+%?Rg z`zuJgohx+lN&|T`vno~L+}9BI85#mH>ZCW?mdlS(DPMC?gTmWjZs_^|n&a$9{L;n8l}sKWunA)LO@m8Unl7^r!AAXSMsMo%7ZCAWjz+EZn7( z_AOb9ht5U_lM)D-ObtpcqM84JaL8w&i+mub&h(b)MgHp;+aY&XW&X$Q=oALuzL9ft z;=YyW9W@qIF&&DRQpg~bsMS8r-KCVEn;f9|@kLUhPqLDAq9zlSQAoVc;iO?hs%-T*KQo9hNj*2+z3~t?0RjVq_{Ty|h zl8@tf4I27bQCg9>l(aE+VSZkCfZ>k#N688_PjsjNF=_}6?KTS10;orCRzh`5M`}8~ zTP~aCrJ`u#C!=|mi=2~cZ$49bB-geaO-0DmT=^!)tQtY4e5BuLOV8o1Wdrz{l$l`v z@u#vTbDoQ6rP0W=*!Khj`V$>3tM=eK>eu^Zm0^1Ee61-Qdc2=a6S&8*B@am9uwkl7 zN6ADLu_evFDm{3l6}H9rG*NTvY+8Ka*bq;oww8Jea{(cs_QUx1+dqVsO|B*1@xFJ} zNIQyRUa5QvM|8QQ+-23P2F;7S%!+#)mH76~tG911VTHn(h4wo!lu3D1u}uP@*Qw;X z7WaC;DRXI7@hihM6VZ&w#eyGLl#Rcsa?GJGE4pb1s8^4>YM%D)~-0 z8od~NBVSEUBa`^K9gU~1Gb?O;qSsPaJ5cf-U#p#=QLaB9j|lhg%)rw$+nQyt z-u4dJjw5C*%k`|_G76U*`w!=tO2A{gbXzd`>!3RAl$N{JawrjeT8Axn*NzBSJCd>f zYSnyL>^iTf@f5$^j~=%p7Qu>*lYk$`Y)fey{id!SAMX2AEKO=#1fpZj+3g&gJZ%aN z%PWqzg#7N8=yI|Ij0>?!cQm{YqeGV^;A-Aj?3AwgI^?$0B2?ra1gHD%qm26jE@8b9 zDbr5Tdg(Yb_0k9C4;$2?(ohpxx6EZUyKs?uRR?Yy&+V0Ix;7I7ToHC~+y;nC^ zWIhx=&5X>*k9YC7ynRobbN0*)scTcBX-i*o-!lj#_iX!i%X!xscET;!`e=VuZJ&!S zcfJ22yC-a_d&HWTT!E2@V>hrn4WEGbZ+sWOpI^aL1gC3+X_6Npqd30T$|dwK84RTXGi_v3chl!xci) zXMPG4^tRa3&xSMCR9NUr%KxHSAIfc}b%I?OIC|qZ;WyC-;*CLcl&JTF)$A>fY!;>- zS3^wws5Y&ZSv!h&&TLu-bA>5{j8g|8KgAf_GGCabAFy6*o&5k29$+f(nB>z5iwhXO z>ex(3^6z>rvl7t3yDfn7t0{aE7;ORx^{0)I6eX@T_12DU3w%hZq}f?T*?%=z_?b7u z?sZ*`GsiuyCW;LJ-45H;hc7n$Yxl z8Tz!XF(>Nki|eCu`yQ(YzOv8J@wTPoI;G=W?^mM+M}KK67Mxh8mzp^LbRD3DWc`ec z(N!xY(w45WMhsZkwL4e1JRke|f+E)F=&R%Y1q)=Bpu5|}d&ZfxfFQGD*(Kw&LWA5{ z`NyAD8Na|X7npp!8-;VNGjE|O3!m-P+!RW%rTOH@k9&VT}ym8|+U!_q+yGV;lNSnYQF}w+GlHgXw_R8lc>l zK0U5TdGPbWj$nfi&N7jB_)PxQ#7(0_=ox_Z4O@5GwyDh=9^TdgYO}ITvhtog^Kjq8 zAmNB^+h3gl&taC1cYIF@YXz{#Yh7tSvG(F8RZr?Wua!vT*!CZOJ*m(nw+}a8susdc z7}#01)3s-qKM>ixxxA$DezR{vHzAEtw%0=K5RM4;6CB}YW;Ir3U=RUdHz4Nm9o%{$$oqysJJGI&jO8ZV_+>7LF z^D3QA{cHLCk`vW&M}cLTdSV-)z_J{3i#G|y{%=$Mu3f`VWZUt{G%Xl$GVDyZ57JE{ea%?C}V=Xl~8sFSy6&tyrTS3rBh6-tCc)HP4S` z9O$bU{miG@?)tVliuKSW0mnt&$%|-9A)0BluzVCY!gd_8T(5kIUpeDt)Z95IL!X&w z7NG9EY%{}w9#8KK)PHRCp7xiN!cxx1$1EwGXW@K{lk3U;#YNoXj_ygO&+iJ(i(G)+p0FNN*l{o#%Qi* zK#{SfB}22#;K3*wfNmT^4}Mdzjh|0IOhaY9qEKfsDM|N%Yy1Wh9UGv zYIXtuMY>;-6F81aO4}(44%;Gnnv9S$e7!7Pm| zBO-$C95~8ryFJl`eJ2Cd((lt*sJXn?80?+#o!Gr>`6J^^202bk^>{@C0|0!q>Aw(e za=ovePaL5_Iz>zym)WA&9=KT>JTvmNHi0g*znl%~DI z1|1{o!ISbuAG_@0MJ?a5mVmCUDF~tDvSe!9!~%=$%cX;=*yu$Aiawj>*8_q-li%BF z<|@K@w239M&c9t6%E`dK1)&b4F`~~iErgg6&Kt&3Vv3Mn?%+MdFl!0VwF&yL3&`8g zR^CY`J6#g9J60;E{iC$@xY8*dkB1~QiH3uTjT8aIK@EK%lf!VdLNI}v{5lTpVN@ND5=4q3%b@KsLYf?M|FQ#xxqn3$aVZ? ztv6GV;3d{fOQjeF6wwyo5V#E+l4Uf*ZC8!Ola7!WHgt4;!lOLE&L0ZfQw*+$;P z2~jW5U6g0v!;1sl*8nz&=}smi<;&TRyn*#j?N(fwLM2yuXIiqpxgqJh0OSj`#!Ql< z$)RjC$SYme_B#TxnlmGl5-gF8>nk?`=3i5k*Ld2f=Ly_D%X+l69o>hE7e`Y9Dn2Bh z|FQJ^Q|;&F0d-h#=6ZMWI;OGyDQIt;UNn&wu#B8Lgq-kwNus!M5G`=cv|RAxrXcgyjp~vyD2QA^beL;t zZEi@ViR*CW)gYQQvshI2R@XQaZdFQ%r^P>QQ==sBe;msNTIp-czdUkVf9>_lGaP7DK5J#%B)_5gLrZBv zdo4=m?*7h;O^qyaZv1`D|7w^*X;??gL4^Uqc1L?EjD6&oHKV9YjE8zqGf%Psd z=6}1|KfSYGu9f@{aPjwj{eOFH_-zqqYE0h-{m;(^e}50qoPeD9JE3FtfJZ-bpXx15 QoW7@{m9>@16s_<7A6&p*0ssI2 literal 1176 zcmeAS@N?(olHy`uVBq!ia0y~yU`zpF7G|KxQ_~n>ASE5(6XJU2%$aT5wjDoyeCN)c zhYue~)IoJr)!_ctc?@IBNOduu#%#gJ{ zktxyDXppmTQb{MLkWMvASIdn{(hU}k{Q`>Lo;>@}8LQK_Cd>cpKVctxSKrrh>#x85 zTD3Rseb3uI{d}jZUjx7Tt@su2KC~n2M9P^ypJR6>oSY#$vuCH`WJ7Bs$zo@<u!XZGBjcyflck>qCO$%f`BMrrP9$$1Ek{~fNa)Pd+R14+q+-kfn?v_xCf zTJwdYR$7-F8QTP4S2#!u@G%zwMLuKlFM&C{uIU(|fqPO#=lRrgLdEVBA6 zq~`pDY5C&7$03DxGNL zqE~QD-qi2diI-=LXYfoo>~0m`+;mcUf^eO=^gNk~muIpcovd7BwfW&mLCd+70XCLO z%Z+c>PreRRZ-3>EqaTx_2+M`}f=8d4xP6z^Q|M%$&>F|U*U-t{aD>^ye0#&Rj;a4n z<~%FET4pv?{8Hkw-{uqkp6dR8;^CR>{YJ{|#_^>UPfqp7oZC~TZCvzc+j$+!sgjoJ z>ilM&n}TOf%=L&XGjnBIZlqkUdG!4*H6Y{sq}lhDojA49=ft(G0Y3xQo;l%Bw0XPc zrb2D~JF12+?kXCXCG}ox@%4 zOw3LN<6@hl#Al~iXU;r4k=r<2@5C9I{+g6$r(R1L%{V-<|4hu!Wo?1?dMSB{_Y``6*?+8{COsmA{| z$FwT{H~K!+{YcH_Lxs;`*7m5|eZHp@r1M+L$nd!Gd_y1&)HUDqx0I1_{-cThb82~K z&dd*gctZXBJx8@?6;G63Y;Hbt=HUtVi*teW#}jGM;vnY=^3R<4T$e8*?Z5Q3Z - Chorus 2 - Kodi web interface + Chorus 2 - CoreELEC web interface diff --git a/addons/webinterface.default/js/kodi-webinterface.js b/addons/webinterface.default/js/kodi-webinterface.js index 2a584e7ac6b..89f703cf396 100644 --- a/addons/webinterface.default/js/kodi-webinterface.js +++ b/addons/webinterface.default/js/kodi-webinterface.js @@ -1,6 +1,6 @@ /*! Chorus 2 - A web interface for Kodi. Created by Jeremy Graham - built on 23-03-2024 */ -!function(t,e){"object"==typeof module&&"object"==typeof module.exports?module.exports=t.document?e(t,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return e(t)}:e(t)}("undefined"!=typeof window?window:this,function(p,t){function e(t,e){return e.toUpperCase()}var h=[],c=h.slice,g=h.concat,a=h.push,r=h.indexOf,n={},i=n.toString,v=n.hasOwnProperty,m={},o="1.11.1",S=function(t,e){return new S.fn.init(t,e)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,l=/^-ms-/,u=/-([\da-z])/gi;function d(t){var e=t.length,n=S.type(t);return"function"!==n&&!S.isWindow(t)&&(!(1!==t.nodeType||!e)||("array"===n||0===e||"number"==typeof e&&0>10|55296,1023&i|56320)}var t,p,w,o,i,g,d,v,_,u,c,m,x,r,y,b,s,a,S,T="sizzle"+-new Date,C=n.document,E=0,f=0,l=rt(),k=rt(),M=rt(),D=function(t,e){return t===e&&(c=!0),0},O="undefined",A={}.hasOwnProperty,e=[],N=e.pop,j=e.push,R=e.push,L=e.slice,I=e.indexOf||function(t){for(var e=0,n=this.length;e+~]|"+F+")"+F+"*"),W=new RegExp("="+F+"*([^\\]'\"]*?)"+F+"*\\]","g"),J=new RegExp(U),G=new RegExp("^"+q+"$"),X={ID:new RegExp("^#("+H+")"),CLASS:new RegExp("^\\.("+H+")"),TAG:new RegExp("^("+H.replace("w","w*")+")"),ATTR:new RegExp("^"+$),PSEUDO:new RegExp("^"+U),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+F+"*(even|odd|(([+-]|)(\\d*)n|)"+F+"*(?:([+-]|)"+F+"*(\\d+)|))"+F+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+F+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+F+"*((?:-\\d)?\\d*)"+F+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,tt=/[+~]/,et=/'|\\/g,nt=new RegExp("\\\\([\\da-f]{1,6}"+F+"?|("+F+")|.)","ig");try{R.apply(e=L.call(C.childNodes),C.childNodes),e[C.childNodes.length].nodeType}catch(t){R={apply:e.length?function(t,e){j.apply(t,L.call(e))}:function(t,e){for(var n=t.length,i=0;t[n++]=e[i++];);t.length=n-1}}}function it(t,e,n,i){var r,o,s,a,l,u,c,h,d,f;if((e?e.ownerDocument||e:C)!==x&&m(e),n=n||[],!t||"string"!=typeof t)return n;if(1!==(a=(e=e||x).nodeType)&&9!==a)return[];if(y&&!i){if(r=Z.exec(t))if(s=r[1]){if(9===a){if(!(o=e.getElementById(s))||!o.parentNode)return n;if(o.id===s)return n.push(o),n}else if(e.ownerDocument&&(o=e.ownerDocument.getElementById(s))&&S(e,o)&&o.id===s)return n.push(o),n}else{if(r[2])return R.apply(n,e.getElementsByTagName(t)),n;if((s=r[3])&&p.getElementsByClassName&&e.getElementsByClassName)return R.apply(n,e.getElementsByClassName(s)),n}if(p.qsa&&(!b||!b.test(t))){if(h=c=T,d=e,f=9===a&&t,1===a&&"object"!==e.nodeName.toLowerCase()){for(u=g(t),(c=e.getAttribute("id"))?h=c.replace(et,"\\$&"):e.setAttribute("id",h),h="[id='"+h+"'] ",l=u.length;l--;)u[l]=h+pt(u[l]);d=tt.test(t)&&dt(e.parentNode)||e,f=u.join(",")}if(f)try{return R.apply(n,d.querySelectorAll(f)),n}catch(t){}finally{c||e.removeAttribute("id")}}}return v(t.replace(B,"$1"),e,n,i)}function rt(){var i=[];return function t(e,n){return i.push(e+" ")>w.cacheLength&&delete t[i.shift()],t[e+" "]=n}}function ot(t){return t[T]=!0,t}function st(t){var e=x.createElement("div");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function at(t,e){for(var n=t.split("|"),i=t.length;i--;)w.attrHandle[n[i]]=e}function lt(t,e){var n=e&&t,i=n&&1===t.nodeType&&1===e.nodeType&&(~e.sourceIndex||1<<31)-(~t.sourceIndex||1<<31);if(i)return i;if(n)for(;n=n.nextSibling;)if(n===e)return-1;return t?1:-1}function ut(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function ct(n){return function(t){var e=t.nodeName.toLowerCase();return("input"===e||"button"===e)&&t.type===n}}function ht(s){return ot(function(o){return o=+o,ot(function(t,e){for(var n,i=s([],t.length,o),r=i.length;r--;)t[n=i[r]]&&(t[n]=!(e[n]=t[n]))})})}function dt(t){return t&&typeof t.getElementsByTagName!==O&&t}for(t in p=it.support={},i=it.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return!!e&&"HTML"!==e.nodeName},m=it.setDocument=function(t){var e,l=t?t.ownerDocument||t:C,n=l.defaultView;return l!==x&&9===l.nodeType&&l.documentElement?(r=(x=l).documentElement,y=!i(l),n&&n!==n.top&&(n.addEventListener?n.addEventListener("unload",function(){m()},!1):n.attachEvent&&n.attachEvent("onunload",function(){m()})),p.attributes=st(function(t){return t.className="i",!t.getAttribute("className")}),p.getElementsByTagName=st(function(t){return t.appendChild(l.createComment("")),!t.getElementsByTagName("*").length}),p.getElementsByClassName=Q.test(l.getElementsByClassName)&&st(function(t){return t.innerHTML="
",t.firstChild.className="i",2===t.getElementsByClassName("i").length}),p.getById=st(function(t){return r.appendChild(t).id=T,!l.getElementsByName||!l.getElementsByName(T).length}),p.getById?(w.find.ID=function(t,e){if(typeof e.getElementById!==O&&y){var n=e.getElementById(t);return n&&n.parentNode?[n]:[]}},w.filter.ID=function(t){var e=t.replace(nt,h);return function(t){return t.getAttribute("id")===e}}):(delete w.find.ID,w.filter.ID=function(t){var n=t.replace(nt,h);return function(t){var e=typeof t.getAttributeNode!==O&&t.getAttributeNode("id");return e&&e.value===n}}),w.find.TAG=p.getElementsByTagName?function(t,e){if(typeof e.getElementsByTagName!==O)return e.getElementsByTagName(t)}:function(t,e){var n,i=[],r=0,o=e.getElementsByTagName(t);if("*"!==t)return o;for(;n=o[r++];)1===n.nodeType&&i.push(n);return i},w.find.CLASS=p.getElementsByClassName&&function(t,e){if(typeof e.getElementsByClassName!==O&&y)return e.getElementsByClassName(t)},s=[],b=[],(p.qsa=Q.test(l.querySelectorAll))&&(st(function(t){t.innerHTML="",t.querySelectorAll("[msallowclip^='']").length&&b.push("[*^$]="+F+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||b.push("\\["+F+"*(?:value|"+P+")"),t.querySelectorAll(":checked").length||b.push(":checked")}),st(function(t){var e=l.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&b.push("name"+F+"*[*^$|!~]?="),t.querySelectorAll(":enabled").length||b.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),b.push(",.*:")})),(p.matchesSelector=Q.test(a=r.matches||r.webkitMatchesSelector||r.mozMatchesSelector||r.oMatchesSelector||r.msMatchesSelector))&&st(function(t){p.disconnectedMatch=a.call(t,"div"),a.call(t,"[s!='']:x"),s.push("!=",U)}),b=b.length&&new RegExp(b.join("|")),s=s.length&&new RegExp(s.join("|")),e=Q.test(r.compareDocumentPosition),S=e||Q.test(r.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,i=e&&e.parentNode;return t===i||!(!i||1!==i.nodeType||!(n.contains?n.contains(i):t.compareDocumentPosition&&16&t.compareDocumentPosition(i)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},D=e?function(t,e){if(t===e)return c=!0,0;var n=!t.compareDocumentPosition-!e.compareDocumentPosition;return n||(1&(n=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!p.sortDetached&&e.compareDocumentPosition(t)===n?t===l||t.ownerDocument===C&&S(C,t)?-1:e===l||e.ownerDocument===C&&S(C,e)?1:u?I.call(u,t)-I.call(u,e):0:4&n?-1:1)}:function(t,e){if(t===e)return c=!0,0;var n,i=0,r=t.parentNode,o=e.parentNode,s=[t],a=[e];if(!r||!o)return t===l?-1:e===l?1:r?-1:o?1:u?I.call(u,t)-I.call(u,e):0;if(r===o)return lt(t,e);for(n=t;n=n.parentNode;)s.unshift(n);for(n=e;n=n.parentNode;)a.unshift(n);for(;s[i]===a[i];)i++;return i?lt(s[i],a[i]):s[i]===C?-1:a[i]===C?1:0},l):x},it.matches=function(t,e){return it(t,null,null,e)},it.matchesSelector=function(t,e){if((t.ownerDocument||t)!==x&&m(t),e=e.replace(W,"='$1']"),p.matchesSelector&&y&&(!s||!s.test(e))&&(!b||!b.test(e)))try{var n=a.call(t,e);if(n||p.disconnectedMatch||t.document&&11!==t.document.nodeType)return n}catch(t){}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(nt,h),t[3]=(t[3]||t[4]||t[5]||"").replace(nt,h),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||it.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&it.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return X.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&J.test(n)&&(e=g(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(nt,h).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=l[t+" "];return e||(e=new RegExp("(^|"+F+")"+t+"("+F+"|$)"))&&l(t,function(t){return e.test("string"==typeof t.className&&t.className||typeof t.getAttribute!==O&&t.getAttribute("class")||"")})},ATTR:function(n,i,r){return function(t){var e=it.attr(t,n);return null==e?"!="===i:!i||(e+="","="===i?e===r:"!="===i?e!==r:"^="===i?r&&0===e.indexOf(r):"*="===i?r&&-1(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function _(t,n,i){if(S.isFunction(n))return S.grep(t,function(t,e){return!!n.call(t,e,t)!==i});if(n.nodeType)return S.grep(t,function(t){return t===n!==i});if("string"==typeof n){if(w.test(n))return S.filter(n,t,i);n=S.filter(n,t)}return S.grep(t,function(t){return 0<=S.inArray(t,n)!==i})}S.filter=function(t,e,n){var i=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===i.nodeType?S.find.matchesSelector(i,t)?[i]:[]:S.find.matches(t,S.grep(e,function(t){return 1===t.nodeType}))},S.fn.extend({find:function(t){var e,n=[],i=this,r=i.length;if("string"!=typeof t)return this.pushStack(S(t).filter(function(){for(e=0;e)[^>]*|#([\w-]*))$/;(S.fn.init=function(t,e){var n,i;if(!t)return this;if("string"!=typeof t)return t.nodeType?(this.context=this[0]=t,this.length=1,this):S.isFunction(t)?void 0!==x.ready?x.ready(t):t(S):(void 0!==t.selector&&(this.selector=t.selector,this.context=t.context),S.makeArray(t,this));if(!(n="<"===t.charAt(0)&&">"===t.charAt(t.length-1)&&3<=t.length?[null,t,null]:C.exec(t))||!n[1]&&e)return!e||e.jquery?(e||x).find(t):this.constructor(e).find(t);if(n[1]){if(e=e instanceof S?e[0]:e,S.merge(this,S.parseHTML(n[1],e&&e.nodeType?e.ownerDocument||e:T,!0)),b.test(n[1])&&S.isPlainObject(e))for(n in e)S.isFunction(this[n])?this[n](e[n]):this.attr(n,e[n]);return this}if((i=T.getElementById(n[2]))&&i.parentNode){if(i.id!==n[2])return x.find(t);this.length=1,this[0]=i}return this.context=T,this.selector=t,this}).prototype=S.fn,x=S(T);var E=/^(?:parents|prev(?:Until|All))/,k={children:!0,contents:!0,next:!0,prev:!0};function M(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}S.extend({dir:function(t,e,n){for(var i=[],r=t[e];r&&9!==r.nodeType&&(void 0===n||1!==r.nodeType||!S(r).is(n));)1===r.nodeType&&i.push(r),r=r[e];return i},sibling:function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n}}),S.fn.extend({has:function(t){var e,n=S(t,this),i=n.length;return this.filter(function(){for(e=0;e
a",m.leadingWhitespace=3===e.firstChild.nodeType,m.tbody=!e.getElementsByTagName("tbody").length,m.htmlSerialize=!!e.getElementsByTagName("link").length,m.html5Clone="<:nav>"!==T.createElement("nav").cloneNode(!0).outerHTML,t.type="checkbox",t.checked=!0,n.appendChild(t),m.appendChecked=t.checked,e.innerHTML="",m.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,n.appendChild(e),e.innerHTML="",m.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,m.noCloneEvent=!0,e.attachEvent&&(e.attachEvent("onclick",function(){m.noCloneEvent=!1}),e.cloneNode(!0).click()),null==m.deleteExpando){m.deleteExpando=!0;try{delete e.test}catch(t){m.deleteExpando=!1}}}(),function(){var t,e,n=T.createElement("div");for(t in{submit:!0,change:!0,focusin:!0})e="on"+t,(m[t+"Bubbles"]=e in p)||(n.setAttribute(e,"t"),m[t+"Bubbles"]=!1===n.attributes[e].expando);n=null}();var J=/^(?:input|select|textarea)$/i,G=/^key/,X=/^(?:mouse|pointer|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,K=/^([^.]*)(?:\.(.+)|)$/;function Q(){return!0}function Z(){return!1}function tt(){try{return T.activeElement}catch(t){}}function et(t){var e=nt.split("|"),n=t.createDocumentFragment();if(n.createElement)for(;e.length;)n.createElement(e.pop());return n}S.event={global:{},add:function(t,e,n,i,r){var o,s,a,l,u,c,h,d,f,p,g,v=S._data(t);if(v){for(n.handler&&(n=(l=n).handler,r=l.selector),n.guid||(n.guid=S.guid++),(s=v.events)||(s=v.events={}),(c=v.handle)||((c=v.handle=function(t){return typeof S===L||t&&S.event.triggered===t.type?void 0:S.event.dispatch.apply(c.elem,arguments)}).elem=t),a=(e=(e||"").match(O)||[""]).length;a--;)f=g=(o=K.exec(e[a])||[])[1],p=(o[2]||"").split(".").sort(),f&&(u=S.event.special[f]||{},f=(r?u.delegateType:u.bindType)||f,u=S.event.special[f]||{},h=S.extend({type:f,origType:g,data:i,handler:n,guid:n.guid,selector:r,needsContext:r&&S.expr.match.needsContext.test(r),namespace:p.join(".")},l),(d=s[f])||((d=s[f]=[]).delegateCount=0,u.setup&&!1!==u.setup.call(t,i,p,c)||(t.addEventListener?t.addEventListener(f,c,!1):t.attachEvent&&t.attachEvent("on"+f,c))),u.add&&(u.add.call(t,h),h.handler.guid||(h.handler.guid=n.guid)),r?d.splice(d.delegateCount++,0,h):d.push(h),S.event.global[f]=!0);t=null}},remove:function(t,e,n,i,r){var o,s,a,l,u,c,h,d,f,p,g,v=S.hasData(t)&&S._data(t);if(v&&(c=v.events)){for(u=(e=(e||"").match(O)||[""]).length;u--;)if(f=g=(a=K.exec(e[u])||[])[1],p=(a[2]||"").split(".").sort(),f){for(h=S.event.special[f]||{},d=c[f=(i?h.delegateType:h.bindType)||f]||[],a=a[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=d.length;o--;)s=d[o],!r&&g!==s.origType||n&&n.guid!==s.guid||a&&!a.test(s.namespace)||i&&i!==s.selector&&("**"!==i||!s.selector)||(d.splice(o,1),s.selector&&d.delegateCount--,h.remove&&h.remove.call(t,s));l&&!d.length&&(h.teardown&&!1!==h.teardown.call(t,p,v.handle)||S.removeEvent(t,f,v.handle),delete c[f])}else for(f in c)S.event.remove(t,f+e[u],n,i,!0);S.isEmptyObject(c)&&(delete v.handle,S._removeData(t,"events"))}},trigger:function(t,e,n,i){var r,o,s,a,l,u,c,h=[n||T],d=v.call(t,"type")?t.type:t,f=v.call(t,"namespace")?t.namespace.split("."):[];if(s=u=n=n||T,3!==n.nodeType&&8!==n.nodeType&&!Y.test(d+S.event.triggered)&&(0<=d.indexOf(".")&&(d=(f=d.split(".")).shift(),f.sort()),o=d.indexOf(":")<0&&"on"+d,(t=t[S.expando]?t:new S.Event(d,"object"==typeof t&&t)).isTrigger=i?2:3,t.namespace=f.join("."),t.namespace_re=t.namespace?new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=n),e=null==e?[t]:S.makeArray(e,[t]),l=S.event.special[d]||{},i||!l.trigger||!1!==l.trigger.apply(n,e))){if(!i&&!l.noBubble&&!S.isWindow(n)){for(a=l.delegateType||d,Y.test(a+d)||(s=s.parentNode);s;s=s.parentNode)h.push(s),u=s;u===(n.ownerDocument||T)&&h.push(u.defaultView||u.parentWindow||p)}for(c=0;(s=h[c++])&&!t.isPropagationStopped();)t.type=1]","i"),ot=/^\s+/,st=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,at=/<([\w:]+)/,lt=/\s*$/g,gt={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:m.htmlSerialize?[0,"",""]:[1,"X
","
"]},vt=et(T).appendChild(T.createElement("div"));function mt(t,e){var n,i,r=0,o=typeof t.getElementsByTagName!==L?t.getElementsByTagName(e||"*"):typeof t.querySelectorAll!==L?t.querySelectorAll(e||"*"):void 0;if(!o)for(o=[],n=t.childNodes||t;null!=(i=n[r]);r++)!e||S.nodeName(i,e)?o.push(i):S.merge(o,mt(i,e));return void 0===e||e&&S.nodeName(t,e)?S.merge([t],o):o}function yt(t){W.test(t.type)&&(t.defaultChecked=t.checked)}function bt(t,e){return S.nodeName(t,"table")&&S.nodeName(11!==e.nodeType?e:e.firstChild,"tr")?t.getElementsByTagName("tbody")[0]||t.appendChild(t.ownerDocument.createElement("tbody")):t}function wt(t){return t.type=(null!==S.find.attr(t,"type"))+"/"+t.type,t}function _t(t){var e=ft.exec(t.type);return e?t.type=e[1]:t.removeAttribute("type"),t}function xt(t,e){for(var n,i=0;null!=(n=t[i]);i++)S._data(n,"globalEval",!e||S._data(e[i],"globalEval"))}function St(t,e){if(1===e.nodeType&&S.hasData(t)){var n,i,r,o=S._data(t),s=S._data(e,o),a=o.events;if(a)for(n in delete s.handle,s.events={},a)for(i=0,r=a[n].length;i")?o=t.cloneNode(!0):(vt.innerHTML=t.outerHTML,vt.removeChild(o=vt.firstChild)),!(m.noCloneEvent&&m.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||S.isXMLDoc(t)))for(i=mt(o),a=mt(t),s=0;null!=(r=a[s]);++s)i[s]&&Tt(r,i[s]);if(e)if(n)for(a=a||mt(t),i=i||mt(o),s=0;null!=(r=a[s]);s++)St(r,i[s]);else St(t,o);return 0<(i=mt(o,"script")).length&&xt(i,!l&&mt(t,"script")),i=a=r=null,o},buildFragment:function(t,e,n,i){for(var r,o,s,a,l,u,c,h=t.length,d=et(e),f=[],p=0;p")+c[2],r=c[0];r--;)a=a.lastChild;if(!m.leadingWhitespace&&ot.test(o)&&f.push(e.createTextNode(ot.exec(o)[0])),!m.tbody)for(r=(o="table"!==l||lt.test(o)?""!==c[1]||lt.test(o)?0:a:a.firstChild)&&o.childNodes.length;r--;)S.nodeName(u=o.childNodes[r],"tbody")&&!u.childNodes.length&&o.removeChild(u);for(S.merge(f,a.childNodes),a.textContent="";a.firstChild;)a.removeChild(a.firstChild);a=d.lastChild}else f.push(e.createTextNode(o));for(a&&d.removeChild(a),m.appendChecked||S.grep(mt(f,"input"),yt),p=0;o=f[p++];)if((!i||-1===S.inArray(o,i))&&(s=S.contains(o.ownerDocument,o),a=mt(d.appendChild(o),"script"),s&&xt(a),n))for(r=0;o=a[r++];)dt.test(o.type||"")&&n.push(o);return a=null,d},cleanData:function(t,e){for(var n,i,r,o,s=0,a=S.expando,l=S.cache,u=m.deleteExpando,c=S.event.special;null!=(n=t[s]);s++)if((e||S.acceptData(n))&&(o=(r=n[a])&&l[r])){if(o.events)for(i in o.events)c[i]?S.event.remove(n,i):S.removeEvent(n,i,o.handle);l[r]&&(delete l[r],u?delete n[a]:typeof n.removeAttribute!==L?n.removeAttribute(a):n[a]=null,h.push(r))}}}),S.fn.extend({text:function(t){return z(this,function(t){return void 0===t?S.text(this):this.empty().append((this[0]&&this[0].ownerDocument||T).createTextNode(t))},null,t,arguments.length)},append:function(){return this.domManip(arguments,function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||bt(this,t).appendChild(t)})},prepend:function(){return this.domManip(arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=bt(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return this.domManip(arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return this.domManip(arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},remove:function(t,e){for(var n,i=t?S.filter(t,this):this,r=0;null!=(n=i[r]);r++)e||1!==n.nodeType||S.cleanData(mt(n)),n.parentNode&&(e&&S.contains(n.ownerDocument,n)&&xt(mt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){for(var t,e=0;null!=(t=this[e]);e++){for(1===t.nodeType&&S.cleanData(mt(t,!1));t.firstChild;)t.removeChild(t.firstChild);t.options&&S.nodeName(t,"select")&&(t.options.length=0)}return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return S.clone(this,t,e)})},html:function(t){return z(this,function(t){var e=this[0]||{},n=0,i=this.length;if(void 0===t)return 1===e.nodeType?e.innerHTML.replace(it,""):void 0;if("string"==typeof t&&!ct.test(t)&&(m.htmlSerialize||!rt.test(t))&&(m.leadingWhitespace||!ot.test(t))&&!gt[(at.exec(t)||["",""])[1].toLowerCase()]){t=t.replace(st,"<$1>");try{for(;n")).appendTo(e.documentElement))[0].contentWindow||Ct[0].contentDocument).document).write(),e.close(),n=Mt(t,e),Ct.detach()),kt[t]=n),n}m.shrinkWrapBlocks=function(){return null!=Et?Et:(Et=!1,(e=T.getElementsByTagName("body")[0])&&e.style?(t=T.createElement("div"),(n=T.createElement("div")).style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",e.appendChild(n).appendChild(t),typeof t.style.zoom!==L&&(t.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",t.appendChild(T.createElement("div")).style.width="5px",Et=3!==t.offsetWidth),e.removeChild(n),Et):void 0);var t,e,n};var Ot,At,Nt,jt,Rt,Lt,It,Pt,Ft,Ht=/^margin/,qt=new RegExp("^("+B+")(?!px)[a-z%]+$","i"),$t=/^(top|right|bottom|left)$/;function Ut(e,n){return{get:function(){var t=e();if(null!=t){if(!t)return(this.get=n).apply(this,arguments);delete this.get}}}}function Bt(){var t,e,n,i;(e=T.getElementsByTagName("body")[0])&&e.style&&(t=T.createElement("div"),(n=T.createElement("div")).style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",e.appendChild(n).appendChild(t),t.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",Lt=It=!1,Ft=!0,p.getComputedStyle&&(Lt="1%"!==(p.getComputedStyle(t,null)||{}).top,It="4px"===(p.getComputedStyle(t,null)||{width:"4px"}).width,(i=t.appendChild(T.createElement("div"))).style.cssText=t.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",t.style.width="1px",Ft=!parseFloat((p.getComputedStyle(i,null)||{}).marginRight)),t.innerHTML="
t
",(i=t.getElementsByTagName("td"))[0].style.cssText="margin:0;border:0;padding:0;display:none",(Pt=0===i[0].offsetHeight)&&(i[0].style.display="",i[1].style.display="none",Pt=0===i[0].offsetHeight),e.removeChild(n))}p.getComputedStyle?(Ot=function(t){return t.ownerDocument.defaultView.getComputedStyle(t,null)},At=function(t,e,n){var i,r,o,s,a=t.style;return s=(n=n||Ot(t))?n.getPropertyValue(e)||n[e]:void 0,n&&(""!==s||S.contains(t.ownerDocument,t)||(s=S.style(t,e)),qt.test(s)&&Ht.test(e)&&(i=a.width,r=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=s,s=n.width,a.width=i,a.minWidth=r,a.maxWidth=o)),void 0===s?s:s+""}):T.documentElement.currentStyle&&(Ot=function(t){return t.currentStyle},At=function(t,e,n){var i,r,o,s,a=t.style;return null==(s=(n=n||Ot(t))?n[e]:void 0)&&a&&a[e]&&(s=a[e]),qt.test(s)&&!$t.test(e)&&(i=a.left,(o=(r=t.runtimeStyle)&&r.left)&&(r.left=t.currentStyle.left),a.left="fontSize"===e?"1em":s,s=a.pixelLeft+"px",a.left=i,o&&(r.left=o)),void 0===s?s:s+""||"auto"}),(Nt=T.createElement("div")).innerHTML="
a",(jt=(Rt=Nt.getElementsByTagName("a")[0])&&Rt.style)&&(jt.cssText="float:left;opacity:.5",m.opacity="0.5"===jt.opacity,m.cssFloat=!!jt.cssFloat,Nt.style.backgroundClip="content-box",Nt.cloneNode(!0).style.backgroundClip="",m.clearCloneStyle="content-box"===Nt.style.backgroundClip,m.boxSizing=""===jt.boxSizing||""===jt.MozBoxSizing||""===jt.WebkitBoxSizing,S.extend(m,{reliableHiddenOffsets:function(){return null==Pt&&Bt(),Pt},boxSizingReliable:function(){return null==It&&Bt(),It},pixelPosition:function(){return null==Lt&&Bt(),Lt},reliableMarginRight:function(){return null==Ft&&Bt(),Ft}})),S.swap=function(t,e,n,i){var r,o,s={};for(o in e)s[o]=t.style[o],t.style[o]=e[o];for(o in r=n.apply(t,i||[]),e)t.style[o]=s[o];return r};var Vt=/alpha\([^)]*\)/i,zt=/opacity\s*=\s*([^)]*)/,Wt=/^(none|table(?!-c[ea]).+)/,Jt=new RegExp("^("+B+")(.*)$","i"),Gt=new RegExp("^([+-])=("+B+")","i"),Xt={position:"absolute",visibility:"hidden",display:"block"},Yt={letterSpacing:"0",fontWeight:"400"},Kt=["Webkit","O","Moz","ms"];function Qt(t,e){if(e in t)return e;for(var n=e.charAt(0).toUpperCase()+e.slice(1),i=e,r=Kt.length;r--;)if((e=Kt[r]+n)in t)return e;return i}function Zt(t,e){for(var n,i,r,o=[],s=0,a=t.length;s
a",ue=ae.getElementsByTagName("a")[0],ce=(le=T.createElement("select")).appendChild(T.createElement("option")),se=ae.getElementsByTagName("input")[0],ue.style.cssText="top:1px",m.getSetAttribute="t"!==ae.className,m.style=/top/.test(ue.getAttribute("style")),m.hrefNormalized="/a"===ue.getAttribute("href"),m.checkOn=!!se.value,m.optSelected=ce.selected,m.enctype=!!T.createElement("form").enctype,le.disabled=!0,m.optDisabled=!ce.disabled,(se=T.createElement("input")).setAttribute("value",""),m.input=""===se.getAttribute("value"),se.value="t",se.setAttribute("type","radio"),m.radioValue="t"===se.value;var we=/\r/g;S.fn.extend({val:function(n){var i,t,r,e=this[0];return arguments.length?(r=S.isFunction(n),this.each(function(t){var e;1===this.nodeType&&(null==(e=r?n.call(this,t,S(this).val()):n)?e="":"number"==typeof e?e+="":S.isArray(e)&&(e=S.map(e,function(t){return null==t?"":t+""})),(i=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&"set"in i&&void 0!==i.set(this,e,"value")||(this.value=e))})):e?(i=S.valHooks[e.type]||S.valHooks[e.nodeName.toLowerCase()])&&"get"in i&&void 0!==(t=i.get(e,"value"))?t:"string"==typeof(t=e.value)?t.replace(we,""):null==t?"":t:void 0}}),S.extend({valHooks:{option:{get:function(t){var e=S.find.attr(t,"value");return null!=e?e:S.trim(S.text(t))}},select:{get:function(t){for(var e,n,i=t.options,r=t.selectedIndex,o="select-one"===t.type||r<0,s=o?null:[],a=o?r+1:i.length,l=r<0?a:o?r:0;l").append(S.parseHTML(t)).find(i):t)}).complete(n&&function(t,e){s.each(n,r||[t.responseText,e,t])}),this},S.expr.filters.animated=function(e){return S.grep(S.timers,function(t){return e===t.elem}).length};var an=p.document.documentElement;function ln(t){return S.isWindow(t)?t:9===t.nodeType&&(t.defaultView||t.parentWindow)}S.offset={setOffset:function(t,e,n){var i,r,o,s,a,l,u=S.css(t,"position"),c=S(t),h={};"static"===u&&(t.style.position="relative"),a=c.offset(),o=S.css(t,"top"),l=S.css(t,"left"),r=("absolute"===u||"fixed"===u)&&-1"'`]/g,qi=RegExp(Fi.source),$i=RegExp(Hi.source),Ui=/<%-([\s\S]+?)%>/g,Bi=/<%([\s\S]+?)%>/g,Vi=/<%=([\s\S]+?)%>/g,zi=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,Wi=/\w*$/,Ji=/^\s*function[ \n\r\t]+\w/,Gi=/^0[xX]/,Xi=/^\[object .+?Constructor\]$/,Yi=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,Ki=/($^)/,Qi=/[.*+?^${}()|[\]\/\\]/g,Zi=RegExp(Qi.source),tr=/\bthis\b/,er=/['\n\r\u2028\u2029\\]/g,nr=(t="[A-Z\\xc0-\\xd6\\xd8-\\xde]",e="[a-z\\xdf-\\xf6\\xf8-\\xff]+",RegExp(t+"{2,}(?="+t+e+")|"+t+"?"+e+"|"+t+"+|[0-9]+","g")),ir=" \t\v\f \ufeff\n\r\u2028\u2029 ᠎              ",rr=["Array","ArrayBuffer","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Math","Number","Object","RegExp","Set","String","_","clearTimeout","document","isFinite","parseInt","setTimeout","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","window","WinRTError"],or=-1,sr={};sr[Ei]=sr[ki]=sr[Mi]=sr[Di]=sr[Oi]=sr[Ai]=sr[Ni]=sr[ji]=sr[Ri]=!0,sr[gi]=sr[vi]=sr[Ci]=sr[mi]=sr[yi]=sr[bi]=sr[wi]=sr[n]=sr[_i]=sr[xi]=sr[Si]=sr[i]=sr[Ti]=sr[r]=!1;var ar={};ar[gi]=ar[vi]=ar[Ci]=ar[mi]=ar[yi]=ar[Ei]=ar[ki]=ar[Mi]=ar[Di]=ar[Oi]=ar[_i]=ar[xi]=ar[Si]=ar[Ti]=ar[Ai]=ar[Ni]=ar[ji]=ar[Ri]=!0,ar[bi]=ar[wi]=ar[n]=ar[i]=ar[r]=!1;var lr={leading:!1,maxWait:0,trailing:!1},o={"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","Ç":"C","ç":"c","Ð":"D","ð":"d","È":"E","É":"E","Ê":"E","Ë":"E","è":"e","é":"e","ê":"e","ë":"e","Ì":"I","Í":"I","Î":"I","Ï":"I","ì":"i","í":"i","î":"i","ï":"i","Ñ":"N","ñ":"n","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","Ù":"U","Ú":"U","Û":"U","Ü":"U","ù":"u","ú":"u","û":"u","ü":"u","Ý":"Y","ý":"y","ÿ":"y","Æ":"Ae","æ":"ae","Þ":"Th","þ":"th","ß":"ss"},s={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},a={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},l={function:!0,object:!0},u={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},ur=l[typeof window]&&window!==(this&&this.window)?window:this,c=l[typeof exports]&&exports&&!exports.nodeType&&exports,h=l[typeof module]&&module&&!module.nodeType&&module,d=c&&h&&"object"==typeof global&&global;!d||d.global!==d&&d.window!==d&&d.self!==d||(ur=d);var f=h&&h.exports===c&&c;function cr(t,e){if(t!==e){var n=t==t,i=e==e;if(e>>1,K=L?L.BYTES_PER_ELEMENT:0,Q=r.pow(2,53)-1,Z=R&&new R;function tt(t){if(Sr(t)&&!dn(t)){if(t instanceof et)return t;if(b.call(t,"__wrapped__"))return new et(t.__wrapped__,t.__chain__,at(t.__actions__))}return new et(t)}function et(t,e,n){this.__actions__=n||[],this.__chain__=!!e,this.__wrapped__=t}var nt=tt.support={};function it(t){this.actions=null,this.dir=1,this.dropCount=0,this.filtered=!1,this.iteratees=null,this.takeCount=J,this.views=null,this.wrapped=t}function rt(){this.__data__={}}function ot(t){var e=t?t.length:0;for(this.data={hash:P(null),set:new O};e--;)this.push(t[e])}function st(t,e){var n=t.data;return("string"==typeof e||mn(e)?n.set.has(e):n.hash[e])?0:-1}function at(t,e){var n=-1,i=t.length;for(e=e||M(i);++n>>1,s=t[o];(n?s<=e:s=li)return t}else Se=0;return Wt(t,e)});function Ee(t){var e,n;return!(!Sr(t)||w.call(t)!=xi||!(b.call(t,"constructor")||"function"!=typeof(e=t.constructor)||e instanceof e))&&(Lt(t,function(t,e){n=e}),void 0===n||b.call(t,n))}function ke(t){for(var e=Mn(t),n=e.length,i=n&&t.length,r=tt.support,o=i&&be(i)&&(dn(t)||r.nonEnumArgs&&hn(t)),s=-1,a=[];++s>>0,i=M(n);++e=(t=fr(t)).length)return t;var s=i-r.length;if(s<1)return r;var a=t.slice(0,s);if(null==o)return a+r;if(_n(o)){if(t.slice(s).search(o)){var l,u,c=t.slice(0,s);for(o.global||(o=m(o.source,(Wi.exec(o)||"")+"g")),o.lastIndex=0;l=o.exec(c);)u=l.index;a=a.slice(0,null==u?s:u)}}else if(t.indexOf(o,s)!=s){var h=a.lastIndexOf(o);-1").attr(t);this.setElement(e,!1)}}}),a.sync=function(t,e,n){var i=v[t];x.defaults(n=n||{},{emulateHTTP:a.emulateHTTP,emulateJSON:a.emulateJSON});var r={type:i,dataType:"json"};if(n.url||(r.url=x.result(e,"url")||A()),null!=n.data||!e||"create"!==t&&"update"!==t&&"patch"!==t||(r.contentType="application/json",r.data=JSON.stringify(n.attrs||e.toJSON(n))),n.emulateJSON&&(r.contentType="application/x-www-form-urlencoded",r.data=r.data?{model:r.data}:{}),n.emulateHTTP&&("PUT"===i||"DELETE"===i||"PATCH"===i)){r.type="POST",n.emulateJSON&&(r.data._method=i);var o=n.beforeSend;n.beforeSend=function(t){if(t.setRequestHeader("X-HTTP-Method-Override",i),o)return o.apply(this,arguments)}}"GET"===r.type||n.emulateJSON||(r.processData=!1),"PATCH"===r.type&&g&&(r.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")});var s=n.xhr=a.ajax(x.extend(r,n));return e.trigger("request",e,s,n),s};var g=!("undefined"==typeof window||!window.ActiveXObject||window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent),v={create:"POST",update:"PUT",patch:"PATCH",delete:"DELETE",read:"GET"};a.ajax=function(){return a.$.ajax.apply(a.$,arguments)};var m=a.Router=function(t){(t=t||{}).routes&&(this.routes=t.routes),this._bindRoutes(),this.initialize.apply(this,arguments)},y=/\((.*?)\)/g,b=/(\(\?)?:\w+/g,w=/\*\w+/g,_=/[\-{}\[\]+?.,\\\^$|#\s]/g;x.extend(m.prototype,o,{initialize:function(){},route:function(n,i,r){x.isRegExp(n)||(n=this._routeToRegExp(n)),x.isFunction(i)&&(r=i,i=""),r=r||this[i];var o=this;return a.history.route(n,function(t){var e=o._extractParameters(n,t);o.execute(r,e),o.trigger.apply(o,["route:"+i].concat(e)),o.trigger("route",i,e),a.history.trigger("route",o,i,e)}),this},execute:function(t,e){t&&t.apply(this,e)},navigate:function(t,e){return a.history.navigate(t,e),this},_bindRoutes:function(){if(this.routes){this.routes=x.result(this,"routes");for(var t,e=x.keys(this.routes);null!=(t=e.pop());)this.route(t,this.routes[t])}},_routeToRegExp:function(t){return t=t.replace(_,"\\$&").replace(y,"(?:$1)?").replace(b,function(t,e){return e?t:"([^/?]+)"}).replace(w,"([^?]*?)"),new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var n=t.exec(e).slice(1);return x.map(n,function(t,e){return e===n.length-1?t||null:t?decodeURIComponent(t):null})}});var C=a.History=function(){this.handlers=[],x.bindAll(this,"checkUrl"),"undefined"!=typeof window&&(this.location=window.location,this.history=window.history)},E=/^[#\/]|\s+$/g,k=/^\/+|\/+$/g,M=/msie [\w.]+/,D=/\/$/,O=/#.*$/;C.started=!1,x.extend(C.prototype,o,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(null==t)if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var n=this.root.replace(D,"");t.indexOf(n)||(t=t.slice(n.length))}else t=this.getHash();return t.replace(E,"")},start:function(t){if(C.started)throw new Error("Backbone.history has already been started");C.started=!0,this.options=x.extend({root:"/"},this.options,t),this.root=this.options.root,this._wantsHashChange=!1!==this.options.hashChange,this._wantsPushState=!!this.options.pushState,this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var e=this.getFragment(),n=document.documentMode,i=M.exec(navigator.userAgent.toLowerCase())&&(!n||n<=7);if(this.root=("/"+this.root+"/").replace(k,"/"),i&&this._wantsHashChange){var r=a.$('