Appreciate the work! I will give this a try over the upcoming long weekend, or earlier if I find time. I don't have the right debug environment for this either, so building a demo apk each time to load on the shield to debug the old fashion way (hopefully there's good logging at least?) is already giving me ptsd flashbacksyusesope wrote: ↑Tue May 19, 2020 5:14 pmHello guys!
Today I had two hours free so I took a look at the ExoPlayer code.
I state that I do not program in Java, I do not know how the MKV container is structured (until now I did not know what "CodecPrivate" was) and I do not have an Android device capable of playing movies in 4K and especially with Dolby Vision.
The written code is clearly ugly AF but it does its dirty job (you can't expect more from one who writes lines of code as a hobby).
In the LogCat, I read:I think I did it but I can't continue with the code debugging because my device doesn't have the correct hardware decoder.Code: Select all
com.google.android.exoplayer2.demo D / EventLogger: [] Track: 0, id = 1, mimeType = video/dolby-vision, codecs = dvhe.07.06, res = 3840x2160, supported = NO_UNSUPPORTED_TYPE
So Programmers take one step forward (if you clearly own a new Shield 2019).
- Clone the ExoPlayer source code (HERE) and install Android Studio (HERE)
- Before you start, edit the media.exolist.json file (ExoPlayer-release-v2\demos\main\src\main\assets\) as follows:
Clearly replace the IP_ADDR, PORT and TEST_FILE.mkv entries and set up an HTTP server on your PC. You can use whatever you want (Apache, Nginx, etc ...). The simplest solution is to open a command window inside the folder that contains the file to be tested and run:Code: Select all
[ { "name": "Dolby Vision inside the MKV container", "samples": [ { "name": "Test File", "uri": "http://IP_ADDR:PORT/TEST_FILE.mkv", "extension": "mkv" } ] } ]
If you downloaded my tool you can use the interpreter present in the "python-3.7.6.amd64" folder.Code: Select all
python -m http.server
Be careful though: if you use the Python server, do your tests with small files (60 sec). The server does not support partial requests (Byte-range request) so you would have to download dozens of GB of data before starting playback.- HERE you will find the archive with the three files that I modified.
Replacewith those in the archive.Code: Select all
ExoPlayer-release-v2\library\core\src\main\java\com\google\android\exoplayer2\video\HevcConfig.java ExoPlayer-release-v2\library\core\src\main\java\com\google\android\exoplayer2\extractor\mkv\MatroskaExtractor.java ExoPlayer-release-v2\demos\main\src\main\AndroidManifest.xml
- Now open Android Studio, Import Project (Gradle, etc ...), select the folder "ExoPlayer-release-v2", wait for the IDE to finish its configuration and then start a debug session or directly build an apk (demo app) to be installed on the Shield.
The basic idea is to emulate the behavior of the "parseVideoSampleEntry" method in the "AtomParsers" class (ExoPlayer-release-v2\library\core\src\main\java\com\google\android\exoplayer2\extractor\mp4\AtomParsers.java).
In particular:Code: Select all
else if (childAtomType == Atom.TYPE_dvcC || childAtomType == Atom.TYPE_dvvC) { DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent); if (dolbyVisionConfig != null) { codecs = dolbyVisionConfig.codecs; mimeType = MimeTypes.VIDEO_DOLBY_VISION; } }
From what I understand mike has positioned the Atom boxes that interest us within the CodecPrivate area.
So, inside the MatroskaExtractor class I added the "findDolbyVisionBox" method.The latter searches for one of the boxes of interest and if one is found it calculates the size and returns the content (otherwise an empty box is returned)Code: Select all
private byte[] findDolbyVisionBox(byte[] codecPrivate) { byte[] dvccBox = {100, 118, 99, 67}; byte[] dvvcBox = {100, 118, 118, 99}; byte[] emptyBox = {}; byte[] boxSize = new byte[4]; ArrayList<byte[]> DolbyVisionBoxesType = new ArrayList<byte[]>( Arrays.asList(dvccBox, dvvcBox) ); for (int indexBoxType = 0; indexBoxType < DolbyVisionBoxesType.size(); indexBoxType++) { byte[] boxType = DolbyVisionBoxesType.get(indexBoxType); for (int pos = 0; pos <= codecPrivate.length - boxType.length; pos++) { int j = 0; while (j < boxType.length && codecPrivate[pos + j] == boxType[j]) { j++; } if (j == boxType.length) { System.arraycopy(codecPrivate,pos - boxSize.length, boxSize, 0, boxSize.length); byte[] boxContent = new byte[ByteBuffer.wrap(boxSize).getInt()]; System.arraycopy(codecPrivate,pos + boxType.length, boxContent, 0, boxContent.length); return boxContent; } } } return emptyBox; }
I used the new method inside "initializeOutput".
At the point where the configuration for the HEVC stream is initialized ("case CODEC_ID_H265:") I added:Code: Select all
byte[] dolbyVisionBoxContent = findDolbyVisionBox(codecPrivate); if (dolbyVisionBoxContent.length > 0) { DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(new ParsableByteArray(dolbyVisionBoxContent)); if (dolbyVisionConfig != null) { codecs = dolbyVisionConfig.codecs; mimeType = MimeTypes.VIDEO_DOLBY_VISION; } }
I have doubts:
- Is there a better way to parse "CodecPrivate"?
On-line I have not found anything about it.- I realized that the "HevcConfig" class finishes analyzing codecPrivate right in correspondence of the dvcC box. So I modified the "HevcConfig" class to set the "dataPos" field i've added with the last buffer position at the end of the codecPrivate parsing.
With the last known position it is much easier to analyze the remaining part of codecPrivate: I created the "logMyDoubts" method which is equivalent to "findDolbyVisionBox" but much more efficient (returns an error log visible in LogCat).I don't know if the behavior I have observed is always reproducible:Code: Select all
private void logMyDoubts(byte[] codecPrivate, int buffPos){ int lengthUnprocessedData = codecPrivate.length - buffPos; byte[] unprocessedData = new byte[lengthUnprocessedData]; System.arraycopy(codecPrivate, buffPos, unprocessedData, 0, unprocessedData.length); ParsableByteArray unprocessedDataParsableByteArray = new ParsableByteArray(unprocessedData); int boxSize = unprocessedDataParsableByteArray.readInt(); String boxType = unprocessedDataParsableByteArray.readString(4); byte[] boxContent = new byte[boxSize - 8]; unprocessedDataParsableByteArray.readBytes(boxContent,0,boxContent.length); Log.e("yusesope msg",String.format("I've found %s",boxType)); }
mike, do the atom boxes just follow one after another until the end of codecPrivate?- The hvcE box was not taken into consideration by the developers of ExoPlayer. We have to understand how to use it!
![Laughing :lol:](./images/smilies/icon_lol.gif)