[FFmpeg-devel] [PATCH 1/2] avformat/mxf: support MCA audio information

Marton Balint cus at passwd.hu
Sat Nov 13 13:19:54 EET 2021



On Wed, 10 Nov 2021, Marc-Antoine Arnaud wrote:

> ---
> doc/demuxers.texi     |  10 ++
> libavformat/mxf.h     |   1 +
> libavformat/mxfdec.c  | 284 +++++++++++++++++++++++++++++++++++++++++-
> libavformat/version.h |   2 +-
> 4 files changed, 290 insertions(+), 7 deletions(-)

[...]

> @@ -2688,6 +2841,98 @@ static int mxf_parse_structural_metadata(MXFContext *mxf)
>                 sti->need_parsing = AVSTREAM_PARSE_FULL;
>             }
>             st->codecpar->bits_per_coded_sample = av_get_bits_per_sample(st->codecpar->codec_id);
> +
> +            current_channel = 0;
> +
> +            if (descriptor->channels >= FF_SANE_NB_CHANNELS) {
> +                av_log(mxf->fc, AV_LOG_ERROR, "max number of channels %d reached\n", FF_SANE_NB_CHANNELS);
> +                return AVERROR_INVALIDDATA;
> +            }
> +
> +            for (j = 0; j < descriptor->channels; ++j) {
> +                source_track->channel_ordering[j] = j;
> +            }
> +
> +            for (j = 0; j < descriptor->sub_descriptors_count; j++) {
> +                MXFMCASubDescriptor *mca_sub_descriptor = mxf_resolve_strong_ref(mxf, &descriptor->sub_descriptors_refs[j], MCASubDescriptor);
> +                if (mca_sub_descriptor == NULL) {
> +                    continue;
> +                }
> +
> +                // Soundfield group
> +                if (IS_KLV_KEY(mca_sub_descriptor->mca_label_dictionary_id, mxf_soundfield_group)) {
> +                    const MXFSoundfieldGroupUL* group_ptr = mxf_soundfield_groups;
> +
> +                    while (group_ptr->uid[0]) {
> +                        if (IS_KLV_KEY(group_ptr->uid, mca_sub_descriptor->mca_label_dictionary_id)) {
> +                            st->codecpar->channel_layout = group_ptr->id;
> +                            break;
> +                        }
> +                        group_ptr++;
> +                    }
> +                }
> +
> +                // Audio channel
> +                if (IS_KLV_KEY(mca_sub_descriptor->mca_label_dictionary_id, mxf_audio_channel)) {
> +                    const MXFChannelOrderingUL* channel_ordering_ptr = mxf_channel_ordering;
> +
> +                    while (channel_ordering_ptr->uid[0]) {
> +                        if (IS_KLV_KEY(channel_ordering_ptr->uid, mca_sub_descriptor->mca_label_dictionary_id)) {
> +                            source_track->channel_ordering[current_channel] = channel_ordering_ptr->index;

Same what I wrote for the earlier version:

You should check if current_channel < desciptor->channels here, and if 
not, then warn the user and break out of the loop. Otherwise 
current_channel can grow out of array limits.

It should also be checked that channel_ordering_ptr->index < 
descriptor->channels, and if not, then similarly, warn the user and break 
out.

Thomas mentioned he prefers a hard failure, returning 
AVERROR_INVALIDDATA in addition to printing the error.

> +
> +                            if(channel_ordering_ptr->service_type != AV_AUDIO_SERVICE_TYPE_NB) {
> +                                enum AVAudioServiceType *ast;
> +                                uint8_t* side_data = av_stream_new_side_data(st, AV_PKT_DATA_AUDIO_SERVICE_TYPE, sizeof(*ast));
> +                                if (!side_data)
> +                                    goto fail_and_free;
> +                                ast = (enum AVAudioServiceType*)side_data;
> +                                *ast = channel_ordering_ptr->service_type;
> +                            }
> +
> +                            current_channel += 1;
> +                            break;
> +                        }
> +                        channel_ordering_ptr++;
> +                    }
> +                }
> +
> +                // set language from MCA spoken language information
> +                if (mca_sub_descriptor->language) {
> +                    ret = set_language(mxf->fc, mca_sub_descriptor->language, &st->metadata);
> +                    if (ret < 0) {
> +                        return ret;
> +                    }
> +                }
> +            }
> +
> +            // check if the mapping is not required
> +            source_track->require_reordering = 0;
> +            if (is_pcm(st->codecpar->codec_id)) {
> +                for (j = 0; j < descriptor->channels; ++j) {
> +                    if (source_track->channel_ordering[j] != j) {
> +                        source_track->require_reordering = 1;
> +                        break;
> +                    }
> +                }
> +            } else {
> +                st->codecpar->channel_layout = 0;
> +            }

This is wrong if codec is not pcm but no reordering is required 
because it destroys the otherwise valid channel layout. Also it does not 
reset the channel layout if the user want no reordering but it is needed.

So I meant something like this:

for (j = 0; j < descriptor->channels; ++j) {
     if (source_track->channel_ordering[j] != j) {
         if (!is_pcm(st->codecpar->codec_id) || mxf->skip_audio_reordering) {
             av_log(mxf->fc, AV_LOG_WARNING, "Skipping channel reordering!\n");
             st->codecpar->channel_layout = 0;
         } else {
             source_track->require_reordering = 1;
         }
         break;
     }
}


> +
> +            if (source_track->require_reordering) {
> +                current_channel = 0;
> +                av_log(mxf->fc, AV_LOG_DEBUG, "MCA Audio mapping (");
> +                for(j = 0; j < descriptor->channels; ++j) {
> +                    for(int k = 0; k < descriptor->channels; ++k) {
> +                        if(source_track->channel_ordering[k] == current_channel) {
> +                            av_log(mxf->fc, AV_LOG_DEBUG, "%d -> %d", source_track->channel_ordering[k], k);
> +                            if (current_channel != descriptor->channels - 1)
> +                                av_log(mxf->fc, AV_LOG_DEBUG, ", ");
> +                            current_channel += 1;
> +                        }
> +                    }
> +                }
> +                av_log(mxf->fc, AV_LOG_DEBUG, ")\n");
> +            }
>         } else if (st->codecpar->codec_type == AVMEDIA_TYPE_DATA) {
>             enum AVMediaType type;
>             container_ul = mxf_get_codec_ul(mxf_data_essence_container_uls, essence_container_ul);
> @@ -2888,6 +3133,8 @@ static const MXFMetadataReadTableEntry mxf_metadata_read_table[] = {
>     { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x5c,0x00 }, mxf_read_generic_descriptor, sizeof(MXFDescriptor), Descriptor }, /* VANC/VBI - SMPTE 436M */
>     { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x5e,0x00 }, mxf_read_generic_descriptor, sizeof(MXFDescriptor), Descriptor }, /* MPEG2AudioDescriptor */
>     { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x64,0x00 }, mxf_read_generic_descriptor, sizeof(MXFDescriptor), Descriptor }, /* DC Timed Text Descriptor */
> +    { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x6c,0x00 }, mxf_read_mca_sub_descriptor, sizeof(MXFMCASubDescriptor), MCASubDescriptor }, /* Soundfield Group Label Subdescriptor */
> +    { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x6b,0x00 }, mxf_read_mca_sub_descriptor, sizeof(MXFMCASubDescriptor), MCASubDescriptor }, /* Audio Channel Label Subdescriptor */
>     { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x3A,0x00 }, mxf_read_track, sizeof(MXFTrack), Track }, /* Static Track */
>     { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x3B,0x00 }, mxf_read_track, sizeof(MXFTrack), Track }, /* Generic Track */
>     { { 0x06,0x0e,0x2b,0x34,0x02,0x53,0x01,0x01,0x0d,0x01,0x01,0x01,0x01,0x01,0x14,0x00 }, mxf_read_timecode_component, sizeof(MXFTimecodeComponent), TimecodeComponent },
> @@ -3187,12 +3434,6 @@ static void mxf_compute_essence_containers(AVFormatContext *s)
>     }
> }
>
> -static int is_pcm(enum AVCodecID codec_id)
> -{
> -    /* we only care about "normal" PCM codecs until we get samples */
> -    return codec_id >= AV_CODEC_ID_PCM_S16LE && codec_id < AV_CODEC_ID_PCM_S24DAUD;
> -}
> -
> static MXFIndexTable *mxf_find_index_table(MXFContext *mxf, int index_sid)
> {
>     int i;
> @@ -3619,6 +3860,25 @@ static int mxf_set_pts(MXFContext *mxf, AVStream *st, AVPacket *pkt)
>     return 0;
> }
>
> +static int mxf_audio_remapping(int* channel_ordering, uint8_t* data, int size, int sample_size, int channels)
> +{
> +    int sample_offset = channels * sample_size;
> +    int number_of_samples = size / sample_offset;
> +    uint8_t tmp[FF_SANE_NB_CHANNELS * 4];
> +    uint8_t* data_ptr = data;
> +
> +    for (int sample = 0; sample < number_of_samples; ++sample) {
> +        memcpy(tmp, data_ptr, sample_offset);
> +
> +        for (int channel = 0; channel < channels; ++channel) {
> +            memcpy(&data_ptr[sample_size * channel_ordering[channel]], &tmp[sample_size * channel],  sample_size);
> +        }
> +
> +        data_ptr += sample_offset;
> +    }
> +    return 0;
> +}
> +
> static int mxf_read_packet(AVFormatContext *s, AVPacket *pkt)
> {
>     KLVPacket klv;
> @@ -3733,6 +3993,15 @@ static int mxf_read_packet(AVFormatContext *s, AVPacket *pkt)
>                 return ret;
>             }
>
> +            // for audio, process audio remapping if MCA label requires it

Trailing whitespace at end of line.

> +            if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && track->require_reordering && !mxf->skip_audio_reordering) {

And you can remove the mxf->skip_audio_reordering check form here if it is 
only set if mxf->skip_audio_reordering is not set.

Thanks,
Marton


More information about the ffmpeg-devel mailing list