Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sample] [Media] - Implementation of "UltraHDR to HDR Video" #83

Merged
merged 7 commits into from
Aug 21, 2023

Conversation

madebymozart
Copy link
Contributor

Description

This CL contains sample code for converting a series of UltraHDR images into an HDR video using GPU hardware acceleration. When rendering an UltraHDR image into a 10-bit frame, the gain map is considered.

This implementation is very detailed as there is many moving parts of it, do excessive notes have been added to guide developer through the various steps needed to make this a reality.

How Has This Been Tested?

Test Configuration #1 - Pixel 7 Pro
Test Configuration #2 - Pixel 7

Checklist:

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation

Dem

ultrahdr_to_hdrvideo_screen_recording.mp4

Resulting File

This file have been taken from device and depending on the device you view it on, you will see it in true 10-bit HDR HLG.
https://github.com/android/platform-samples/assets/5649571/934421b8-6059-4828-8686-b3bec05a26d8

@madebymozart madebymozart added enhancement New feature or request new sample This denotes the creation of a new sample in the platform samples app labels Aug 16, 2023
@madebymozart madebymozart self-assigned this Aug 16, 2023
samples/media/ultrahdr/README.md Outdated Show resolved Hide resolved
* [ImageWriter.setOnImageReleasedListener] callbacks.
*/
private lateinit var imageWriterHandler: Handler
private val imageWriterThread: HandlerThread by lazy {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use coroutines instead? This sample mixes coroutines with threads and it's a bit confusing.

Copy link
Contributor Author

@madebymozart madebymozart Aug 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, ImageWriter and MediaCodec.Callback need their own handlers to process callback calls on their own thread.

Not sure if or when they will switch to coroutine scopes if ever.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not familiar with this APIs but I think if you pass null to the handler it will use the thread handler
https://developer.android.com/reference/android/media/ImageWriter#setOnImageReleasedListener(android.media.ImageWriter.OnImageReleasedListener,%20android.os.Handler)

If you then call this method within a coroutine using a Dispatcher.IO shouldn't that work?

In anycase shouldn't you stop the thread on stop/destroy?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can create a Dispatcher from a Handler using Handler.asCoroutineDispatcher()

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah going through again the above suggestion won't change anything as you have to pass a Handler to the API.

tags = ["UltraHDR"],
)
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class UltraHDRToHDRVideo : Fragment() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider moving the concurrency and processing bits into a separate state holder class. As it stands, the UI and its state are really strongly coupled.

CoroutineScope(Dispatchers.IO).launch {
val threadName = Thread.currentThread().name
Log.i(TAG, "Starting Conversion to Video on $threadName")
doImageToVideoConversion()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This coroutine is launched on the IO Dispatcher, and subsequently used withContext to also place it on the same dispatcher. The latter alone should be enough.

@mmoczkowski mmoczkowski self-requested a review August 17, 2023 12:37
Copy link

@mmoczkowski mmoczkowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend the following:

  1. Use Compose for UI
  2. Increase separation of concerns by abstracting the video processing to a separate class.
tags = ["UltraHDR"],
)
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class UltraHDRToHDRVideo : Fragment() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered using Compose? It is required that all new samples are written in Compose.

*
* Using [AtomicBoolean] since it can be accessed by multiple threads.
*/
private var isFrameBufferProcessed = AtomicBoolean(true)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The video processing code is mixed with presentation code. I highly recommend to abstract video processing to a separate class.

* The [MediaCodec.Callback], used to asynchronously process frame produced by the
* [ImageWriter].
*/
private fun setUpMediaCodecCallback() = object : MediaCodec.Callback() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be beneficial to move this class to its own file. By doing so, we can inject it as a dependency into the relevant video processor. This would align better with the Single Responsibility Principle, ensuring each class has a clear, distinct function. This change would likely enhance code readability and maintainability. What do you think?

// Create path to cache directory. Delete any previously generated videos.
val dir = File(folderPath)
if (dir.exists() && dir.isDirectory) dir.listFiles()?.let {
for (file in it) file.delete()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use File::deleteRecursively here?


bitmaps.forEach { bitmap ->
var frameCount = 1
while (frameCount <= FORMAT_FRAME_RATE) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could repeat(FORMAT_FRAME_RATE) be sufficient?

/**
* Folder path name where .mp4 files will be encoded to
*/
private val FOLDER_PATH_NAME = UltraHDRToHDRVideo::class.java.simpleName

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be cautious here. If the code obfuscation is enabled the name of the class will be changed to something random. I think it's best to simply hardcode a string.

mmoczkowski

This comment was marked as duplicate.

@mmoczkowski mmoczkowski dismissed their stale review August 21, 2023 07:29

Talked to PR author

@madebymozart madebymozart merged commit 1b30ef0 into main Aug 21, 2023
@madebymozart madebymozart deleted the ultrahdr-to-video branch August 21, 2023 21:20
@madebymozart
Copy link
Contributor Author

Will add a second PR what uses the same logic that will split video processing from sample @mmoczkowski @marcelpinto :)

yrezgui pushed a commit that referenced this pull request Dec 7, 2023
* Testing Implementation for UltraHDR to HDR Video

* Complete HW aceel implementation

* Add temp muxer to write file

* working implementation of output

* Clean up implementation of UltraHDR to HDR Video

* Slight fixes to implementation, better coroutines

* pr nits
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request new sample This denotes the creation of a new sample in the platform samples app
4 participants