Взаимное преобразование формата кодового потока AVCC/HVCC и Приложениеb. Практика аудио- и видеоиндустрии.
Взаимное преобразование формата кодового потока AVCC/HVCC и Приложениеb. Практика аудио- и видеоиндустрии.

Ключевая информация, используемая для декодирования в кодовом потоке H.264, включает в себя SPS и PPS, тогда как кодовый поток H.265 включает в себя VPS, SPS и PPS. Кодовый поток H.264 имеет два формата: AVCC и Приложение B, а кодовый поток H.265 соответствует двум форматам: HVCC и Приложение B. Обычно в инженерной практике при декодировании MP4 по умолчанию используются форматы кодового потока AVCC и HVCC. Однако, поскольку декодер платформы Android обычно поддерживает только формат ПриложениеB, на этом этапе необходимо преобразовать формат кодового потока. В этой статье мы покажем, как использовать код для преобразования между форматами кодового потока AVCC/HVCC и ПриложениеB.

1. Определение формата данных H.264/H.265 NALU

1) Структура данных H.264 AVCDecoderConfigurationRecord

AVCDecoderConfigurationRecord Прямо сейчас AVC Sequence Заголовок является общим SPS、PPS Упаковано как ExtraData Структура данных, инкапсулированная ExtraData можно поместить в FLV и MP4 В заголовке файла проигрыватель получает его и использует для инициализации декодера.

Язык кода:javascript
копировать
aligned(8) class AVCDecoderConfigurationRecord {
    unsigned int(8) configurationVersion = 1;
    unsigned int(8) AVCProfileIndication;
    unsigned int(8) profile_compatibility;
    unsigned int(8) AVCLevelIndication;
    bit(6) reserved = ‘111111’b;
    // lengthSizeMinusOne + 1 выражать NALU Length Size,Прямо сейчас один NALU Сколько байтвыражать используется для длины?,Обычно 4 байт
    unsigned int(2) lengthSizeMinusOne;
    bit(3) reserved = ‘111’b;
    // sps из числа
    unsigned int(5) numOfSequenceParameterSets;
    for (i=0; i< numOfSequenceParameterSets; i++) {
        // Два байтвыражать один sps длина
        unsigned int(16) sequenceParameterSetLength ;
        // sps из содержания
        bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
    }
    // pps из числа
    unsigned int(8) numOfPictureParameterSets;
    for (i=0; i< numOfPictureParameterSets; i++) {
        // Два байтвыражать один pps длина
        unsigned int(16) pictureParameterSetLength;
        // pps из содержания
        bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
    }
}

2) Структура данных H.265 HEVCDecoderConfigurationRecord

Эта структура данных используется для VPS、SPS、PPS Упаковано как ExtraData,Хорошо упаковано ExtraData можно поместить в FLV и MP4 В заголовке файла проигрыватель получает его и использует для инициализации декодера.

Язык кода:javascript
копировать
class HEVCDecoderConfigurationRecord {
    unsigned int(8)  configurationVersion;
    unsigned int(2)  general_profile_space;
    unsigned int(1)  general_tier_flag;
    unsigned int(5)  general_profile_idc;
    unsigned int(32) general_profile_compatibility_flags;
    unsigned int(48) general_constraint_indicator_flags;
    unsigned int(8)  general_level_idc;
    bit(4) reserved = ‘1111’b;
    unsigned int(12) min_spatial_segmentation_idc;
    bit(6) reserved = ‘111111’b;
    unsigned int(2)  parallelismType;
    bit(6) reserved = ‘111111’b;
    unsigned int(2)  chromaFormat;
    bit(5) reserved = ‘11111’b;
    unsigned int(3)  bitDepthLumaMinus8;
    bit(5) reserved = ‘11111’b;
    unsigned int(3)  bitDepthChromaMinus8;
    bit(16) avgFrameRate;
    bit(2)  constantFrameRate;
    bit(3)  numTemporalLayers;
    bit(1)  temporalIdNested;
    // lengthSizeMinusOne + 1 выражать NALU Length Size,Прямо сейчас один NALU Сколько байтвыражать используется для длины?,Обычно 4 байт
    unsigned int(2) lengthSizeMinusOne;
    // длина массива
    unsigned int(8) numOfArrays;
    for (j=0; j < numOfArrays; j++) {
        bit(1) array_completeness;
        unsigned int(1)  reserved = 0;
        // NALU из Тип
        unsigned int(6)  NAL_unit_type;
        // выше NALU Тип из количества
        unsigned int(16) numNalus;
        for (i=0; i< numNalus; i++) {
            unsigned int(16) nalUnitLength;
            bit(8*nalUnitLength) nalUnit;
        }
    }
    // ...
}

3) Формат Приложения B

Помимо использования H.264 из AVCDecoderConfigurationRecord формат и H.265 из HEVCDecoderConfigurationRecord формат для инкапсуляции SPS, PPS, VPS, также можно использовать StartCode разделить VPS、SPS、PPS。

Язык кода:javascript
копировать
[start code] NALU | [start code] NALU | ...

Этот формат более распространен, он используется перед каждым кадром. 0x 00 00 00 01 или 0x 00 00 01 в качестве стартового кода. H.264 Когда данные кодового потока хранятся в виде файла, обычно это формат, подобный из, и перед каждым кадром должен быть начальный код. СПС, ППС как категория NALU Сохраненный в этом потоке кода, он обычно размещается в начале потока кода. То есть этот формат содержит VCL Кадзуи VCL Тип из NALU。

2. Базовый исходный код для взаимного преобразования между AVCC/HVCC и Приложением B.

1) Преобразовать запись AVCDecoderConfigurationRecord в Приложение B.

Чтобы реализовать H.264 Преобразование между двумя типами данных Формат требует зависимостей. SPS、PPS как средство。Общий процесс преобразования:AVCDecoderConfigurationRecord <-> SPS、PPS <-> 00 00 00 01 SPS 00 00 00 01 PPS

Подробную информацию о реализации см. FFmpeg из libavcodec/h264_mp4toannexb_bsf.c в файле h264_extradata_to_annexb функция:

Язык кода:javascript
копировать
static int h264_extradata_to_annexb(AVBSFContext *ctx, const int padding)
{
    H.264BSFContext *s = ctx->priv_data;
    GetByteContext ogb, *gb = &ogb;
    uint16_t unit_size;
    uint32_t total_size                 = 0;
    uint8_t *out                        = NULL, unit_nb, sps_done = 0;
    static const uint8_t nalu_header[4] = { 0, 0, 0, 1 };
    int length_size, pps_offset = 0;

    bytestream2_init(gb, ctx->par_in->extradata, ctx->par_in->extradata_size);

    bytestream2_skipu(gb, 4);

    /* retrieve length coded size */
    length_size = (bytestream2_get_byteu(gb) & 0x3) + 1;

    /* retrieve sps and pps unit(s) */
    unit_nb = bytestream2_get_byteu(gb) & 0x1f; /* number of sps unit(s) */
    if (!unit_nb) {
        goto pps;
    }

    while (unit_nb--) {
        int err;

        /* possible overread ok due to padding */
        unit_size   = bytestream2_get_be16u(gb);
        total_size += unit_size + 4;
        av_assert1(total_size <= INT_MAX - padding);
        if (bytestream2_get_bytes_left(gb) < unit_size + !sps_done) {
            av_log(ctx, AV_LOG_ERROR, "Global extradata truncated, "
                   "corrupted stream or invalid MP4/AVCC bitstream\n");
            av_free(out);
            return AVERROR_INVALIDDATA;
        }
        if ((err = av_reallocp(&out, total_size + padding)) < 0)
            return err;
        memcpy(out + total_size - unit_size - 4, nalu_header, 4);
        bytestream2_get_bufferu(gb, out + total_size - unit_size, unit_size);
pps:
        if (!unit_nb && !sps_done++) {
            unit_nb = bytestream2_get_byteu(gb); /* number of pps unit(s) */
            pps_offset = total_size;
        }
    }

    if (out)
        memset(out + total_size, 0, padding);

    if (pps_offset) {
        s->sps      = out;
        s->sps_size = pps_offset;
    } else {
        av_log(ctx, AV_LOG_WARNING,
               "Warning: SPS NALU missing or invalid. "
               "The resulting stream may not play.\n");
    }
    if (pps_offset < total_size) {
        s->pps      = out + pps_offset;
        s->pps_size = total_size - pps_offset;
    } else {
        av_log(ctx, AV_LOG_WARNING,
               "Warning: PPS NALU missing or invalid. "
               "The resulting stream may not play.\n");
    }

    av_freep(&ctx->par_out->extradata);
    ctx->par_out->extradata      = out;
    ctx->par_out->extradata_size = total_size;

    return length_size;
}

2) Приложение B преобразовано в AVCDecoderConfigurationRecord.

libavformat/avc.c Документы ff_isom_write_avcc Функция отвечает за AVStream->par->extradata серединаиз SPS、PPS упакованный в AVCDecoderConfigurationRecord

ff_isom_write_avcc Основная логика - суждение AVStream->par->extradata Какова форма? СПС, ППС, если AVStream->par->extradata Да AnnexB Сплит из СПС, ППС, потом сначала извлекаем СПС, ППС, далее следуйте AVCDecoderConfigurationRecord Формат записи, иначе можно написать напрямую.

Язык кода:javascript
копировать
int ff_isom_write_avcc(AVIOContext *pb, const uint8_t *data, int len)
{
    AVIOContext *sps_pb = NULL, *pps_pb = NULL, *sps_ext_pb = NULL;
    uint8_t *buf, *end, *start;
    uint8_t *sps, *pps, *sps_ext;
    uint32_t sps_size = 0, pps_size = 0, sps_ext_size = 0;
    int ret, nb_sps = 0, nb_pps = 0, nb_sps_ext = 0;

    if (len <= 6)
        return AVERROR_INVALIDDATA;

    /* check for H.264 start code */
    if (AV_RB32(data) != 0x00000001 &&
        AV_RB24(data) != 0x000001) {
        avio_write(pb, data, len);
        return 0;
    }

    ret = ff_avc_parse_nal_units_buf(data, &buf, &len);
    if (ret < 0)
        return ret;
    start = buf;
    end = buf + len;

    ret = avio_open_dyn_buf(&sps_pb);
    if (ret < 0)
        goto fail;
    ret = avio_open_dyn_buf(&pps_pb);
    if (ret < 0)
        goto fail;
    ret = avio_open_dyn_buf(&sps_ext_pb);
    if (ret < 0)
        goto fail;

    /* look for sps and pps */
    while (end - buf > 4) {
        uint32_t size;
        uint8_t nal_type;
        size = FFMIN(AV_RB32(buf), end - buf - 4);
        buf += 4;
        nal_type = buf[0] & 0x1f;

        if (nal_type == 7) { /* SPS */
            nb_sps++;
            if (size > UINT16_MAX || nb_sps >= H.264_MAX_SPS_COUNT) {
                ret = AVERROR_INVALIDDATA;
                goto fail;
            }
            avio_wb16(sps_pb, size);
            avio_write(sps_pb, buf, size);
        } else if (nal_type == 8) { /* PPS */
            nb_pps++;
            if (size > UINT16_MAX || nb_pps >= H.264_MAX_PPS_COUNT) {
                ret = AVERROR_INVALIDDATA;
                goto fail;
            }
            avio_wb16(pps_pb, size);
            avio_write(pps_pb, buf, size);
        } else if (nal_type == 13) { /* SPS_EXT */
            nb_sps_ext++;
            if (size > UINT16_MAX || nb_sps_ext >= 256) {
                ret = AVERROR_INVALIDDATA;
                goto fail;
            }
            avio_wb16(sps_ext_pb, size);
            avio_write(sps_ext_pb, buf, size);
        }

        buf += size;
    }
    sps_size = avio_get_dyn_buf(sps_pb, &sps);
    pps_size = avio_get_dyn_buf(pps_pb, &pps);
    sps_ext_size = avio_get_dyn_buf(sps_ext_pb, &sps_ext);

    if (sps_size < 6 || !pps_size) {
        ret = AVERROR_INVALIDDATA;
        goto fail;
    }

    avio_w8(pb, 1); /* version */
    avio_w8(pb, sps[3]); /* profile */
    avio_w8(pb, sps[4]); /* profile compat */
    avio_w8(pb, sps[5]); /* level */
    avio_w8(pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */
    avio_w8(pb, 0xe0 | nb_sps); /* 3 bits reserved (111) + 5 bits number of sps */

    avio_write(pb, sps, sps_size);
    avio_w8(pb, nb_pps); /* number of pps */
    avio_write(pb, pps, pps_size);

    if (sps[3] != 66 && sps[3] != 77 && sps[3] != 88) {
        H.264SPS seq;
        ret = ff_avc_decode_sps(&seq, sps + 3, sps_size - 3);
        if (ret < 0)
            goto fail;

        avio_w8(pb, 0xfc |  seq.chroma_format_idc); /* 6 bits reserved (111111) + chroma_format_idc */
        avio_w8(pb, 0xf8 | (seq.bit_depth_luma - 8)); /* 5 bits reserved (11111) + bit_depth_luma_minus8 */
        avio_w8(pb, 0xf8 | (seq.bit_depth_chroma - 8)); /* 5 bits reserved (11111) + bit_depth_chroma_minus8 */
        avio_w8(pb, nb_sps_ext); /* number of sps ext */
        if (nb_sps_ext)
            avio_write(pb, sps_ext, sps_ext_size);
    }

fail:
    ffio_free_dyn_buf(&sps_pb);
    ffio_free_dyn_buf(&pps_pb);
    ffio_free_dyn_buf(&sps_ext_pb);
    av_free(start);

    return ret;
}

3) Запись конфигурации HEVCDecoderConfigurationRecord преобразована в Приложение B.

Чтобы реализовать H.265 Преобразование между двумя типами данных Формат требует зависимостей. VPS、SPS、PPS как средство。Общий процесс преобразования:HEVCDecoderConfigurationRecord <-> VPS、SPS、PPS <-> 00 00 00 01 VPS 00 00 00 01 SPS 00 00 00 01 PPS

Подробную информацию о реализации см. FFmpeg из libavcodec/hevc_mp4toannexb_bsf.c в файле hevc_extradata_to_annexb функция:

Язык кода:javascript
копировать
static int hevc_extradata_to_annexb(AVBSFContext *ctx)
{
    GetByteContext gb;
    int length_size, num_arrays, i, j;
    int ret = 0;

    uint8_t *new_extradata = NULL;
    size_t   new_extradata_size = 0;

    bytestream2_init(&gb, ctx->par_in->extradata, ctx->par_in->extradata_size);

    bytestream2_skip(&gb, 21);
    length_size = (bytestream2_get_byte(&gb) & 3) + 1;
    num_arrays  = bytestream2_get_byte(&gb);

    for (i = 0; i < num_arrays; i++) {
        int type = bytestream2_get_byte(&gb) & 0x3f;
        int cnt  = bytestream2_get_be16(&gb);

        if (!(type == HEVC_NAL_VPS || type == HEVC_NAL_SPS || type == HEVC_NAL_PPS ||
              type == HEVC_NAL_SEI_PREFIX || type == HEVC_NAL_SEI_SUFFIX)) {
            av_log(ctx, AV_LOG_ERROR, "Invalid NAL unit type in extradata: %d\n",
                   type);
            ret = AVERROR_INVALIDDATA;
            goto fail;
        }

        for (j = 0; j < cnt; j++) {
            int nalu_len = bytestream2_get_be16(&gb);

            if (4 + AV_INPUT_BUFFER_PADDING_SIZE + nalu_len > SIZE_MAX - new_extradata_size) {
                ret = AVERROR_INVALIDDATA;
                goto fail;
            }
            ret = av_reallocp(&new_extradata, new_extradata_size + nalu_len + 4 + AV_INPUT_BUFFER_PADDING_SIZE);
            if (ret < 0)
                goto fail;

            AV_WB32(new_extradata + new_extradata_size, 1); // add the startcode
            bytestream2_get_buffer(&gb, new_extradata + new_extradata_size + 4, nalu_len);
            new_extradata_size += 4 + nalu_len;
            memset(new_extradata + new_extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
        }
    }

    av_freep(&ctx->par_out->extradata);
    ctx->par_out->extradata      = new_extradata;
    ctx->par_out->extradata_size = new_extradata_size;

    if (!new_extradata_size)
        av_log(ctx, AV_LOG_WARNING, "No parameter sets in the extradata\n");

    return length_size;
fail:
    av_freep(&new_extradata);
    return ret;
}

4) Приложение B преобразовано в HEVCDecoderConfigurationRecord.

libavformat/hevc.c Документы ff_isom_write_hvcc Функция отвечает за AVStream->par->extradata серединаиз VPS、SPS、PPS упакованный в HEVCDecoderConfigurationRecord,и напишите в HVCC Box.

Язык кода:javascript
копировать
int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data,
                       int size, int ps_array_completeness)
{
    HEVCDecoderConfigurationRecord hvcc;
    uint8_t *buf, *end, *start;
    int ret;

    if (size < 6) {
        /* We can't write a valid hvcC from the provided data */
        return AVERROR_INVALIDDATA;
    } else if (*data == 1) {
        /* Data is already hvcC-formatted */
        avio_write(pb, data, size);
        return 0;
    } else if (!(AV_RB24(data) == 1 || AV_RB32(data) == 1)) {
        /* Not a valid Annex B start code prefix */
        return AVERROR_INVALIDDATA;
    }

    ret = ff_avc_parse_nal_units_buf(data, &start, &size);
    if (ret < 0)
        return ret;

    hvcc_init(&hvcc);

    buf = start;
    end = start + size;

    while (end - buf > 4) {
        uint32_t len = FFMIN(AV_RB32(buf), end - buf - 4);
        uint8_t type = (buf[4] >> 1) & 0x3f;

        buf += 4;

        for (unsigned i = 0; i < FF_ARRAY_ELEMS(hvcc.arrays); i++) {
            static const uint8_t array_idx_to_type[] =
                { HEVC_NAL_VPS, HEVC_NAL_SPS, HEVC_NAL_PPS,
                  HEVC_NAL_SEI_PREFIX, HEVC_NAL_SEI_SUFFIX };

            if (type == array_idx_to_type[i]) {
                ret = hvcc_add_nal_unit(buf, len, ps_array_completeness,
                                        &hvcc, i);
                if (ret < 0)
                    goto end;
                break;
            }
        }

        buf += len;
    }

    ret = hvcc_write(pb, &hvcc);

end:
    hvcc_close(&hvcc);
    av_free(start);
    return ret;
}

3. Реализация проекта взаимного преобразования AVCC/HVCC и Приложение B.

1) Используйте FFmpeg для преобразования AVCDecoderConfigurationRecord и HEVCDecoderConfigurationRecord в Приложение B.

Позвонив AVBitStreamFilterContext Реализация, H.264 Находить h264_mp4toannexb,H.265 Находить hevc_mp4toannexb

Обработка одного кадра AVPacket:

Язык кода:javascript
копировать
void run()
{
    AVBitStreamFilterContext* bsfc = ifmt_ctx->streams[pkt->stream_index]->codec->codec_id == AV_CODEC_ID_H.264 ? av_bitstream_filter_init("h264_mp4toannexb") : av_bitstream_filter_init("hevc_mp4toannexb"); 
    while (running)
    {
        ret = av_read_frame(ifmt_ctx, pkt);
        if(isVideo){
            av_bitstream_filter_filter(bsfc, ifmt_ctx->streams[pkt->stream_index]->codec, NULL, &pkt->data, &pkt->size, pkt->data, pkt->size, 0);
        }
    }
    
    if (bsfc) {
        av_bitstream_filter_close(bsfc);
    }
}

Обработка дополнительных данных:

Язык кода:javascript
копировать
void run()
{
    AVBitStreamFilterContext* bsfc = ifmt_ctx->streams[pkt->stream_index]->codec->codec_id == AV_CODEC_ID_H.264 ? av_bitstream_filter_init("h264_mp4toannexb") : 
    uint8_t *tmpData = NULL;
    int tmpSize = 0;
    av_bitstream_filter_filter(bsfc, stream->codec, NULL, &tmpData, &tmpSize, tmpData, tmpSize, 1);
    BSFCompatContext *compatContext = bsfc->priv_data;
    AVBSFContext *bsfContext = compatContext->ctx;
    tmpData = bsfContext->par_out->extradata;
    tmpSize = bsfContext->par_out->extradata_size;
}

2) Используйте FFmpeg для преобразования Приложение B в AVCDecoderConfigurationRecord, HEVCDecoderConfigurationRecord.

H.264 Позвонивдокумент libavformat/avc.c Документы ff_isom_write_avcc Реализация функции, H.265 Позвонивдокумент libavformat/hevc.c Документы ff_isom_write_hvcc Реализация функции.

Язык кода:javascript
копировать
void run()
{
    uint8_t *outExtra = NULL;
    int outExtraSize = 0;
    AVIOContext *pb;
    if (avio_open_dyn_buf(&pb) < 0) {
        return;
    }

    if (format_id == kCMVideoCodecType_HEVC) {
        ff_isom_write_hvcc(pb, annexb_extra_data_, annexb_extra_size_, 0);
    } else {
        ff_isom_write_avcc(pb, annexb_extra_data_, annexb_extra_size_);
    }
   outExtraSize = avio_close_dyn_buf(pb, &outExtra);
}
boy illustration
Учебное пособие по Jetpack Compose для начинающих, базовые элементы управления и макет
boy illustration
Код js веб-страницы, фон частицы, код спецэффектов
boy illustration
【новый! Суперподробное】Полное руководство по свойствам компонентов Figma.
boy illustration
🎉Обязательно к прочтению новичкам: полное руководство по написанию мини-программ WeChat с использованием программного обеспечения Cursor.
boy illustration
[Забавный проект Docker] VoceChat — еще одно приложение для мгновенного чата (IM)! Может быть встроен в любую веб-страницу!
boy illustration
Как реализовать переход по странице в HTML (html переходит на указанную страницу)
boy illustration
Как решить проблему зависания и низкой скорости при установке зависимостей с помощью npm. Существуют ли доступные источники npm, которые могут решить эту проблему?
boy illustration
Серия From Zero to Fun: Uni-App WeChat Payment Practice WeChat авторизует вход в систему и украшает страницу заказа, создает интерфейс заказа и инициирует запрос заказа
boy illustration
Серия uni-app: uni.navigateЧтобы передать скачок значения
boy illustration
Апплет WeChat настраивает верхнюю панель навигации и адаптируется к различным моделям.
boy illustration
JS-время конвертации
boy illustration
Обеспечьте бесперебойную работу ChromeDriver 125: советы по решению проблемы chromedriver.exe не найдены
boy illustration
Поле комментария, щелчок мышью, специальные эффекты, js-код
boy illustration
Объект массива перемещения объекта JS
boy illustration
Как открыть разрешение на позиционирование апплета WeChat_Как использовать WeChat для определения местонахождения друзей
boy illustration
Я даю вам два набора из 18 простых в использовании фонов холста Power BI, так что вам больше не придется возиться с цветами!
boy illustration
Получить текущее время в js_Как динамически отображать дату и время в js
boy illustration
Вам необходимо изучить сочетания клавиш vsCode для форматирования и организации кода, чтобы вам больше не приходилось настраивать формат вручную.
boy illustration
У ChatGPT большое обновление. Всего за 45 минут пресс-конференция показывает, что OpenAI сделал еще один шаг вперед.
boy illustration
Copilot облачной разработки — упрощение разработки
boy illustration
Микросборка xChatGPT с низким кодом, создание апплета чат-бота с искусственным интеллектом за пять шагов
boy illustration
CUDA Out of Memory: идеальное решение проблемы нехватки памяти CUDA
boy illustration
Анализ кластеризации отдельных ячеек, который должен освоить каждый&MarkerгенетическийВизуализация
boy illustration
vLLM: мощный инструмент для ускорения вывода ИИ
boy illustration
CodeGeeX: мощный инструмент генерации кода искусственного интеллекта, который можно использовать бесплатно в дополнение к второму пилоту.
boy illustration
Машинное обучение Реальный бой LightGBM + настройка параметров случайного поиска: точность 96,67%
boy illustration
Бесшовная интеграция, мгновенный интеллект [1]: платформа больших моделей Dify-LLM, интеграция без кодирования и встраивание в сторонние системы, более 42 тысяч звезд, чтобы стать свидетелями эксклюзивных интеллектуальных решений.
boy illustration
LM Studio для создания локальных больших моделей
boy illustration
Как определить количество слоев и нейронов скрытых слоев нейронной сети?
boy illustration
[Отслеживание целей] Подробное объяснение ByteTrack и детали кода