Hello 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:
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
I think I did it but I can't continue with the code debugging because my device doesn't have the correct hardware decoder.
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:
Code: Select all
[
{
"name": "Dolby Vision inside the MKV container",
"samples": [
{
"name": "Test File",
"uri": "http://IP_ADDR:PORT/TEST_FILE.mkv",
"extension": "mkv"
}
]
}
]
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:
If you downloaded my tool you can use the interpreter present in the "python-3.7.6.amd64" folder.
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.
Replace
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
with those in the archive.
- 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.
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;
}
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)
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).
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));
}
I don't know if the behavior I have observed is always reproducible:
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!