Skip to content

Parallel CinemaDNG export + Dual ISO optimizations and RCD interpolation - #316

Open
fijha wants to merge 20 commits into
ilia3101:masterfrom
fijha:Optimizations
Open

Parallel CinemaDNG export + Dual ISO optimizations and RCD interpolation#316
fijha wants to merge 20 commits into
ilia3101:masterfrom
fijha:Optimizations

Conversation

@fijha

@fijha fijha commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

A pretty significant export speed improvement (especially for Dual ISO).

Parallel CinemaDNG export:

  • Based on Parallel DNG export #312 and Make Cinema DNG frames export multithread #297.
  • Includes fixes for white/black or partially corrupted frames, random app crashes related to dark-frame subtraction, and issues inside the applyLLRawProcObject() function.
  • Smoother, less jittery ETA estimation.
  • Safe with Dual ISO #pragma omp parallel processing, because by default omp_get_nested() == 0 and omp_get_max_active_levels == 1.
  • Seems stable, but further testing is appreciated.

Dual ISO optimizations:

  • Small but noticeable improvements.
  • Not as scary as it looks :) (carefully tested).

Dual ISO RCD interpolation:

  • This was actually easier than expected. Only a small fix was needed to make RCD work with Dual ISO or any bit depth.
  • The fixed 16-bit 65536.f scale was unnecessary – it’s actually better without it. No precision is lost.

@fijha fijha changed the title Parallel CinemaDNG export + Dual ISO optimizations Parallel CinemaDNG export + Dual ISO optimizations and RCD interpolation Feb 27, 2026
@masc4ii

masc4ii commented Feb 28, 2026

Copy link
Copy Markdown
Collaborator

Thank you for another PR!

In the first test, I got one very wrong frame in a 250 frames export: the frame was cropped in x and y. Never have seen something like this.
In a second test, I got a white frame, same as in my try #312

For the RCD thing: so you now took the librtprocess RCD in the same way as AMaZE, and it works with the small patch you did in librtprocess? Funny. But I can't tell, why they did this 16bit thing...
However, the RCD interpolation seems to work fine. Faster than AMaZE but without noticable different output.

@fijha

fijha commented Feb 28, 2026

Copy link
Copy Markdown
Contributor Author

I'm still doing some tests. I don't get any white/black or cropped frames anymore, but one thing that still concerns me is that multiple threads are reading from the same MLV file.

I think there is an issue with dng_get_frame(). The file_set_pos(), fseek(), and fread() could be the culprits.

Maybe just putting them inside #pragma omp critical could solve the issue. Can you try this?

static int dng_get_frame(mlvObject_t * mlv_data, dngObject_t * dng_data, uint32_t frame_index, const char *prop_filename)
{
    int ret = 0;

    FILE *fd = mlv_data->file[mlv_data->video_index[frame_index].chunk_num];

    if (isMcrawLoaded(mlv_data))
    {
        size_t stored_size;

#pragma omp critical
{
        /* Move to start of frame in file and read the RAW data */
        file_set_pos(fd, mlv_data->video_index[frame_index].block_offset, SEEK_SET);

        mr_item_t item = {};

        if (fread(&item, sizeof(mr_item_t), 1, fd) != 1)
        {
#ifndef STDOUT_SILENT
            printf("Can not read raw frame from %s\n", mlv_data->path);
#endif
            return -1;
        }

        stored_size = item.size;

        if (stored_size > dng_get_image_size(mlv_data, IMG_SIZE_UNPACKED, frame_index)) {
            dng_data->image_buf2 = realloc(dng_data->image_buf2, stored_size);
        }

        if (fread(dng_data->image_buf2, stored_size, 1, fd) != 1)
        {
#ifndef STDOUT_SILENT
            printf("Can not read raw frame from %s\n", mlv_data->path);
#endif
            return -1;
        }
}
        int64_t ret = mr_decode_video_frame((uint8_t*)dng_data->image_buf_unpacked,
                                            (uint8_t*)dng_data->image_buf2,
                                            stored_size,
                                            mlv_data->RAWI.xRes,
                                            mlv_data->RAWI.yRes,
                                            mlv_data->compression_type);

        if (ret <= 0)
        {
#ifndef STDOUT_SILENT
            printf("mcraw decoder: Failed with error code (%ld)\n", ret);
#endif
            return -1;
        }

        /* apply low level raw processing to the unpacked_frame */
        applyLLRawProcObject(mlv_data, dng_data->image_buf_unpacked, dng_data->image_size_unpacked);

        if (dng_data->raw_output_state == COMPRESSED_RAW || dng_data->raw_output_state == COMPRESSED_ORIG)
        {
            ret = dng_compress_image(dng_data->image_buf,
                                     dng_data->image_buf_unpacked,
                                     &dng_data->image_size,
                                     mlv_data->RAWI.xRes,
                                     mlv_data->RAWI.yRes,
                                     mlv_data->RAWI.raw_info.bits_per_pixel);
        }
        else   // uncompressed and fast pass
        {
            dng_pack_image_bits(dng_data->image_buf,
                                dng_data->image_buf_unpacked,
                                mlv_data->RAWI.xRes,
                                mlv_data->RAWI.yRes,
                                mlv_data->RAWI.raw_info.bits_per_pixel,
                                1);
        }
    }
    else
    {
        if (dng_data->raw_input_state == COMPRESSED_RAW) /* If lossless, decompress or pass trough */
        {
#pragma omp critical
{
            /* Move to start of frame in file and read the RAW data */
            file_set_pos(fd, mlv_data->video_index[frame_index].frame_offset, SEEK_SET);

            dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_LOSLESS, frame_index);
            if(fread(dng_data->image_buf, dng_data->image_size, 1, fd) != 1)
            {
#ifndef STDOUT_SILENT
                printf("Can not read raw frame from %s\n", mlv_data->path);
#endif
            }
}
            if(dng_data->raw_output_state == COMPRESSED_ORIG)
            {
                // do nothing, compressed raw data is ready to save unchanged
            }
            else
            {
                ret = dng_decompress_image(dng_data->image_buf_unpacked,
                                           dng_data->image_buf,
                                           dng_data->image_size,
                                           mlv_data->RAWI.xRes,
                                           mlv_data->RAWI.yRes,
                                           mlv_data->RAWI.raw_info.bits_per_pixel);

                /* apply low level raw processing to the unpacked_frame */
                applyLLRawProcObject(mlv_data, dng_data->image_buf_unpacked, dng_data->image_size_unpacked);

                if(dng_data->raw_output_state == COMPRESSED_RAW)
                {
                    ret = dng_compress_image(dng_data->image_buf,
                                             dng_data->image_buf_unpacked,
                                             &dng_data->image_size,
                                             mlv_data->RAWI.xRes,
                                             mlv_data->RAWI.yRes,
                                             (llrpHQDualIso(mlv_data)) ? 16 : mlv_data->RAWI.raw_info.bits_per_pixel);
                }
                else
                {
                    if(!llrpHQDualIso(mlv_data))
                    {
                        dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_PACKED, frame_index);
                        dng_pack_image_bits(dng_data->image_buf,
                                            dng_data->image_buf_unpacked,
                                            mlv_data->RAWI.xRes,
                                            mlv_data->RAWI.yRes,
                                            mlv_data->RAWI.raw_info.bits_per_pixel,
                                            1);
                    }
                    else
                    {
                        dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_UNPACKED, frame_index);
                        memcpy(dng_data->image_buf, dng_data->image_buf_unpacked, dng_data->image_size);
                    }
                }
            }
        }
        else /* If uncompressed, unpack to 16bit or pass trough */
        {
#pragma omp critical
{
            /* Move to start of frame in file and read the RAW data */
            file_set_pos(fd, mlv_data->video_index[frame_index].frame_offset, SEEK_SET);

            dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_PACKED, frame_index);
            if(fread(dng_data->image_buf, dng_data->image_size, 1, fd) != 1)
            {
#ifndef STDOUT_SILENT
                printf("Can not read raw frame from %s\n", mlv_data->path);
#endif
            }
}
            if(dng_data->raw_output_state == UNCOMPRESSED_ORIG)
            {
                dng_reverse_byte_order(dng_data->image_buf, dng_data->image_size);
            }
            else
            {
                dng_unpack_image_bits(dng_data->image_buf_unpacked,
                                      dng_data->image_buf,
                                      mlv_data->RAWI.xRes,
                                      mlv_data->RAWI.yRes,
                                      mlv_data->RAWI.raw_info.bits_per_pixel);

                /* apply low level raw processing to the unpacked_frame */
                applyLLRawProcObject(mlv_data, dng_data->image_buf_unpacked, dng_data->image_size_unpacked);

                if(dng_data->raw_output_state == COMPRESSED_RAW)
                {
                    ret = dng_compress_image(dng_data->image_buf,
                                             dng_data->image_buf_unpacked,
                                             &dng_data->image_size,
                                             mlv_data->RAWI.xRes,
                                             mlv_data->RAWI.yRes,
                                             (llrpHQDualIso(mlv_data)) ? 16 : mlv_data->RAWI.raw_info.bits_per_pixel);
                }
                else
                {
                    if(!llrpHQDualIso(mlv_data))
                    {
                        dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_PACKED, frame_index);
                        dng_pack_image_bits(dng_data->image_buf,
                                            dng_data->image_buf_unpacked,
                                            mlv_data->RAWI.xRes,
                                            mlv_data->RAWI.yRes,
                                            mlv_data->RAWI.raw_info.bits_per_pixel,
                                            1);
                    }
                    else
                    {
                        dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_UNPACKED, frame_index);
                        memcpy(dng_data->image_buf, dng_data->image_buf_unpacked, dng_data->image_size);
                    }
                }
            }
        }
    }

    dng_fill_header(mlv_data, dng_data, frame_index, prop_filename);
    return ret;
}

@fijha

fijha commented Feb 28, 2026

Copy link
Copy Markdown
Contributor Author

Actually, we already have the mlv_data->main_file_mutex, so this might be slightly better:

static int dng_get_frame(mlvObject_t * mlv_data, dngObject_t * dng_data, uint32_t frame_index, const char *prop_filename)
{
    int ret = 0;

    uint16_t chunk = mlv_data->video_index[frame_index].chunk_num;

    FILE *fd = mlv_data->file[chunk];

    pthread_mutex_lock(mlv_data->main_file_mutex + chunk);

    if (isMcrawLoaded(mlv_data))
    {
        /* Move to start of frame in file and read the RAW data */
        file_set_pos(fd, mlv_data->video_index[frame_index].block_offset, SEEK_SET);

        mr_item_t item = {};

        if (fread(&item, sizeof(mr_item_t), 1, fd) != 1)
        {
#ifndef STDOUT_SILENT
            printf("Can not read raw frame from %s\n", mlv_data->path);
#endif
            pthread_mutex_unlock(mlv_data->main_file_mutex + chunk);
            return -1;
        }

        size_t stored_size = item.size;

        if (stored_size > dng_get_image_size(mlv_data, IMG_SIZE_UNPACKED, frame_index)) {
            dng_data->image_buf2 = realloc(dng_data->image_buf2, stored_size);
        }

        if (fread(dng_data->image_buf2, stored_size, 1, fd) != 1)
        {
#ifndef STDOUT_SILENT
            printf("Can not read raw frame from %s\n", mlv_data->path);
#endif
            pthread_mutex_unlock(mlv_data->main_file_mutex + chunk);
            return -1;
        }

        pthread_mutex_unlock(mlv_data->main_file_mutex + chunk);

        int64_t ret = mr_decode_video_frame((uint8_t*)dng_data->image_buf_unpacked,
                                            (uint8_t*)dng_data->image_buf2,
                                            stored_size,
                                            mlv_data->RAWI.xRes,
                                            mlv_data->RAWI.yRes,
                                            mlv_data->compression_type);

        if (ret <= 0)
        {
#ifndef STDOUT_SILENT
            printf("mcraw decoder: Failed with error code (%ld)\n", ret);
#endif
            return -1;
        }

        /* apply low level raw processing to the unpacked_frame */
        applyLLRawProcObject(mlv_data, dng_data->image_buf_unpacked, dng_data->image_size_unpacked);

        if (dng_data->raw_output_state == COMPRESSED_RAW || dng_data->raw_output_state == COMPRESSED_ORIG)
        {
            ret = dng_compress_image(dng_data->image_buf,
                                     dng_data->image_buf_unpacked,
                                     &dng_data->image_size,
                                     mlv_data->RAWI.xRes,
                                     mlv_data->RAWI.yRes,
                                     mlv_data->RAWI.raw_info.bits_per_pixel);
        }
        else   // uncompressed and fast pass
        {
            dng_pack_image_bits(dng_data->image_buf,
                                dng_data->image_buf_unpacked,
                                mlv_data->RAWI.xRes,
                                mlv_data->RAWI.yRes,
                                mlv_data->RAWI.raw_info.bits_per_pixel,
                                1);
        }
    }
    else
    {
        /* Move to start of frame in file and read the RAW data */
        file_set_pos(fd, mlv_data->video_index[frame_index].frame_offset, SEEK_SET);

        if (dng_data->raw_input_state == COMPRESSED_RAW) /* If lossless, decompress or pass trough */
        {
            dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_LOSLESS, frame_index);
            if(fread(dng_data->image_buf, dng_data->image_size, 1, fd) != 1)
            {
#ifndef STDOUT_SILENT
                printf("Can not read raw frame from %s\n", mlv_data->path);
#endif
            }

            pthread_mutex_unlock(mlv_data->main_file_mutex + chunk);

            if(dng_data->raw_output_state == COMPRESSED_ORIG)
            {
                // do nothing, compressed raw data is ready to save unchanged
            }
            else
            {
                ret = dng_decompress_image(dng_data->image_buf_unpacked,
                                           dng_data->image_buf,
                                           dng_data->image_size,
                                           mlv_data->RAWI.xRes,
                                           mlv_data->RAWI.yRes,
                                           mlv_data->RAWI.raw_info.bits_per_pixel);

                /* apply low level raw processing to the unpacked_frame */
                applyLLRawProcObject(mlv_data, dng_data->image_buf_unpacked, dng_data->image_size_unpacked);

                if(dng_data->raw_output_state == COMPRESSED_RAW)
                {
                    ret = dng_compress_image(dng_data->image_buf,
                                             dng_data->image_buf_unpacked,
                                             &dng_data->image_size,
                                             mlv_data->RAWI.xRes,
                                             mlv_data->RAWI.yRes,
                                             (llrpHQDualIso(mlv_data)) ? 16 : mlv_data->RAWI.raw_info.bits_per_pixel);
                }
                else
                {
                    if(!llrpHQDualIso(mlv_data))
                    {
                        dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_PACKED, frame_index);
                        dng_pack_image_bits(dng_data->image_buf,
                                            dng_data->image_buf_unpacked,
                                            mlv_data->RAWI.xRes,
                                            mlv_data->RAWI.yRes,
                                            mlv_data->RAWI.raw_info.bits_per_pixel,
                                            1);
                    }
                    else
                    {
                        dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_UNPACKED, frame_index);
                        memcpy(dng_data->image_buf, dng_data->image_buf_unpacked, dng_data->image_size);
                    }
                }
            }
        }
        else /* If uncompressed, unpack to 16bit or pass trough */
        {
            dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_PACKED, frame_index);
            if(fread(dng_data->image_buf, dng_data->image_size, 1, fd) != 1)
            {
#ifndef STDOUT_SILENT
                printf("Can not read raw frame from %s\n", mlv_data->path);
#endif
            }

            pthread_mutex_unlock(mlv_data->main_file_mutex + chunk);

            if(dng_data->raw_output_state == UNCOMPRESSED_ORIG)
            {
                dng_reverse_byte_order(dng_data->image_buf, dng_data->image_size);
            }
            else
            {
                dng_unpack_image_bits(dng_data->image_buf_unpacked,
                                      dng_data->image_buf,
                                      mlv_data->RAWI.xRes,
                                      mlv_data->RAWI.yRes,
                                      mlv_data->RAWI.raw_info.bits_per_pixel);

                /* apply low level raw processing to the unpacked_frame */
                applyLLRawProcObject(mlv_data, dng_data->image_buf_unpacked, dng_data->image_size_unpacked);

                if(dng_data->raw_output_state == COMPRESSED_RAW)
                {
                    ret = dng_compress_image(dng_data->image_buf,
                                             dng_data->image_buf_unpacked,
                                             &dng_data->image_size,
                                             mlv_data->RAWI.xRes,
                                             mlv_data->RAWI.yRes,
                                             (llrpHQDualIso(mlv_data)) ? 16 : mlv_data->RAWI.raw_info.bits_per_pixel);
                }
                else
                {
                    if(!llrpHQDualIso(mlv_data))
                    {
                        dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_PACKED, frame_index);
                        dng_pack_image_bits(dng_data->image_buf,
                                            dng_data->image_buf_unpacked,
                                            mlv_data->RAWI.xRes,
                                            mlv_data->RAWI.yRes,
                                            mlv_data->RAWI.raw_info.bits_per_pixel,
                                            1);
                    }
                    else
                    {
                        dng_data->image_size = dng_get_image_size(mlv_data, IMG_SIZE_UNPACKED, frame_index);
                        memcpy(dng_data->image_buf, dng_data->image_buf_unpacked, dng_data->image_size);
                    }
                }
            }
        }
    }

    dng_fill_header(mlv_data, dng_data, frame_index, prop_filename);
    return ret;
}

@masc4ii

masc4ii commented Mar 3, 2026

Copy link
Copy Markdown
Collaborator

In new tests I did not get any errors anymore.

It looks there are many many changes - also in dualiso code. I hope you know what you're doing :-D as it worked very nicely in latest PRs.

Why darkframe was changed? You wrote about crashes? How to get them?

For the StatusDialog: the averaging just is usefull if all clips have the same amout of processing, right? In my tests I first had a heavy clip, then a clip with small resolution... for the first the ETA was nice, for the second it was a joke (very very wrong numbers). Haha. ;-)

@fijha

fijha commented Mar 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for testing! The changes in darkframe.c prevent loading and freeing the same dark-frame if it is already loaded. This is important in our parallel processing because the free(video->llrawproc->dark_frame_data); would otherwise cause a race condition and random crashes. It is also an optimization. We don't need to load the same dark-frame for every single frame, but only once or if it has changed.

I also don't get any errors anymore. It works really well. The Dual ISO changes, as crazy as they look :D, are just small optimizations, but together they make a difference in speed. I tested and compared every single change.

@fijha

fijha commented Mar 3, 2026

Copy link
Copy Markdown
Contributor Author

StatusDialog: Yes, the ETA can't be accurate and it is almost impossible to calculate if there are very different clips (especially a mix of Dual ISO and non-Dual ISO, or different resolutions). However, I'm already working on a better status dialog that shows the progress of the current clip as well as the total progress. The averaging will reset with every clip. I also added the ability to pause/resume the CinemaDNG export. Just need a bit more testing :).

Display progress and remaining/elapsed time of the current clip, as well as total progress.

Ability to pause/resume CinemaDNG export.

Use a monospace font for the timer label to prevent text "dancing".

Show a confirmation dialog when clicking the "Abort" button.
@fijha

fijha commented Mar 5, 2026

Copy link
Copy Markdown
Contributor Author

Improved StatusDialog with ability to pause/resume CinemaDNG export:

  • Display progress and remaining/elapsed time of the current clip, as well as total progress.
  • Ability to pause/resume CinemaDNG export.
  • Use a monospace font for the timer label to prevent text "dancing" :)
  • Show a confirmation dialog when clicking the "Abort" button. This also acts as a pause feature for non-CinemaDNG exports, as the confirmation dialog pauses the for loop.

TODO: Calculate theoretical CPU load for each clip based on resolution and recipe settings and use it for accurate total remaining time estimation. I think this is possible :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants