Delphi. Определение разрешения видеофайлов форматов AVI и MKV прямым парсингом без использования кодеков. Часть II.

  Решение данной задачи потребовалось для автоматизированной сортировки файлов указанных форматов при пакетной обработке. При разработке применялись версии Delphi 7 и Windows 7.
  Эта статья в какой-то мере является продолжением предыдущей статьи, а вернее ее расширением. В прошлый раз методом научного тыка удалось собрать парсер получения разрешения видеофайла медиаконтейнера MP4. В этот раз я добавлю в этот парсер обработку медиаконтейнеров AVI и MKV. Т.к. новый парсер базируется на описанном в предыдущей статье, обработку медиаконтейнера MP4 я здесь затрагивать не буду, нового пока ничего не появилось. Так разные косметические мелочи, вроде переименования функций с указанием типа контейнера для которого предназначены.

  Состав нового класса парсера:

{---------------------------- TVideoStream ------------------------------------}
type
  TVideoType = (vtUnk, vtAvi, vtMp4, vtMkv);

type
  TVideoStream = class(TFileStream)
  private
    {------- All routines --------}
    function GetVideoType: TVideoType;
    {------- Mkv routines --------}
    function MkvGetSizeData(var aSize: Int64): Byte;
    function MkvFindTopLevelD(aID: DWord; var aSize: Int64): Boolean;
    function MkvFindInternallD(aID: Byte; var aSize: Int64): Boolean;
    function MkvFindVideoTrack(var aWidth, aHeight: Word; var aSize: Int64): Boolean;
    function ParceMkv(var aWidth, aHeight: Word): Boolean;
    {------- Mp4 routines --------}
    function Mp4ReadSize64: Int64;
    function Mp4ReadAtom(var aSize: DWord): DWord;
    function Mp4GetResolution(var aWidth, aHeight: WORD; aSize: DWord): Boolean;
    function ParceMP4(var aWidth, aHeight: Word): Boolean;
    {------- Avi routines --------}
    function ParceAVI(var aWidth, aHeight: Word): Boolean;
  public
    constructor Create(const aFileName: string);
    function ParceVideoFile(var aWidth, aHeight: Word): Boolean;
  end;

Как уже сказано, функции 'Mp4 routines' вторично описывать не буду, достаточно подробно описаны в предыдущей статье. Здесь будет описываться новое, что появилось.

Определение типа медиаконтейнера.

  Определять тип видео-контейнера всегда лучше по имеющимся в начале файла сигнатурам, это более надежно и правильно чем использовать расширение файла. Рассмотрим с чего начинаются файлы контейнеров AVI, MP4, MKV.
  • AVI — ['RIFF'][Size]['AVI  '] — (три двойных слова), где Size размер всего контейнера без заголовка, Little-Endian;
  • MP4 — [Size]['ftyp'] — (два двойных слова), где Size размер корневого атома ftyp с вместе с сигнатурой, Big-Endian;
  • MKV — [EBML_ID_EBML][Size] — (одно двойное слово идентификатора и поле размера корневого заголовка переменной длины 1..8 байт), Big-Endian;
  Для однозначной идентификации типа контейнера достаточно прочитать два двойных слова начала файла. Рассмотрим в какой последовательности это делать лучше, приведу синатуры идентификаторов в Little-Endian:

const // consts in Little-Endian
  AviType = $46464952; // 'RIFF';
  AviName = $20495641; // 'AVI ';
  Mp4Type = $70797466; // 'ftyp';
  MkvType = $A3DF451A; // EBML_ID_EBML

Тут возможны два спорных варианта — ['RIFF']['ftyp'] и [EBML_ID_EBML]['ftyp'].
  Допустим нам встретился медиаконтейнер с заголовком ['RIFF']['ftyp'], к какому типу он относится? Если прикинуть на MP4 это значит, что атом ftyp имеет размер 0х52494646 байт (Big-Endian), что невалидно, его размер несколько десятков байт, упрощенный парсер наверняка ошибется. Если прикинуть на AVI это значит, что файл контейнера имеет размер 0х70797466 + 8 байт, что вполне допустимо.
  Теперь допустим нам встретился медиаконтейнер с заголовком [EBML_ID_EBML]['ftyp']. Опять же, если прикинуть на MP4 это значит, что атом ftyp имеет размер 0х1A45DFA3 байт (Big-Endian), что опять же невалидно и упрощенный парсер ошибется. Если прикинуть на MKV это значит, что поле размера 2-байтовое и содержит $2674, что нестандартно, но допустимо. При этом $7960 уйдут в следующий идентификатор, что ничего хорошего не обещает. Скорее всего такой заголовок также невалиден.
Отсюда можно сделать вывод, что наличие сигнатуры AVI однозначно позволяет определить тип медиаконтейнера, сигнатуры MKV почти однозначно, с одним исключением, проверку на MP4 лучше провести последней.

В коде для доступа к одному значению без функций преобразования используются следующие записи:

type
  PLong64Rec = ^Long64Rec;
  Long64Rec = packed record
    case Integer of
      0: (Body: Int64);
      1: (Lo, Hi: DWord);
      2: (Words: array[0..3] of Word);
      3: (Bytes: array [0..7] of Byte);
  end;

  PDWordRec = ^DWordRec;
  DWordRec = packed record
    case Integer of
      0: (Body: DWord);
      1: (Lo, Hi: Word);
      2: (Bytes: array [0..3] of Byte);
  end;

Функция определения типа медиаконтейнера:

function TVideoStream.GetVideoType: TVideoType;
const // consts in Little-Endian
  AviType = $46464952; // 'RIFF';
  AviName = $20495641; // 'AVI ';
  MkvType = $A3DF451A; // EBML_ID_EBML
  Mp4Type = $70797466; // 'ftyp';
var
  iSignature: Long64Rec;
  iSize: Int64 absolute iSignature;
begin
  Result := vtUnk;
  if Size > 8 then begin // file not empty ?
    Position := 0;
    ReadBuffer(iSignature, SizeOf(iSignature));
    {---------------------- check type --------------------------}
    if iSignature.Lo = AviType then begin               // 'RIFF'
      ReadBuffer(iSignature.Lo, SizeOf(iSignature.Lo)); // 'AVI '
      if iSignature.Lo = AviName then Result := vtAvi;
    end else
    if iSignature.Lo = MkvType then begin               // EBML_ID_EBML
      Result := vtMkv;
      Seek(SizeOf(MkvType), soFromBeginning);           // back to size
      MkvGetSizeData(iSize);
      Seek(iSize, soFromCurrent);
    end else
    if iSignature.Hi = Mp4Type then begin               // 'ftyp'
      Result := vtMp4;
      iSignature.Hi := SwapEndian32(iSignature.Lo);
      Seek(iSignature.Hi - SizeOf(iSignature), soFromCurrent);
    end;
  end;
end;

  Помимо определения типа медиаконтейнера, функция переходит на конец заголовка (отбрасывает). Сначала тип медиаконтейнера проверяется на AVI, если совпадает, для углубленной проверки и перехода на конец заголовка читается вторая сигнатура 'AVI  '. Если не совпадает тип проверяется на MKV, и затем если не совпал, на медиаконтейнер MP4.

Получение разрешения видеофайла AVI.

  Формат контейнера AVI был разработан гораздо раньше, чем формат контейнеров MP4 и MKV. Еще в 1985 году знаменитая фирма-производитель видеоигрушек Electronic Arts совместно с фирмой Commodore, выпустившей прославленный в то время игровой компьютер Amiga, специально для этой платформы разработали стандарт — Interchange File Format, формат файлов обмена, IFF. Это был первый медиаконтейнер, который мог содержать звук, графику, анимацию, текст и т. п. (о видео тогда, видимо, речь еще не шла). Структура файлов в этом стандарте предполагала разбиение их на блоки (названные создателями chunks — ломти), каждый из которых состоял из заголовка и собственно данных.
  Название формата AVI (Audio Video Inter) переводится как чередование видео и аудио, он был выпущен Microsoft в ноябре 1992 года в пакете Video for Windows. Он представляет собой вариант IFF под названием RIFF (Resource Interchange File Format), и позиционировался в Windows как контейнер для представления потоковых медиаданных — AVI и WAV.
  Декодировать разрешение изображения в AVI гораздо проще чем в MP4 или тем более MKV. В первых байтах AVI-файла всегда содержится информация о контейнере: ['RIFF'][Size]['AVI ']. После них должно идти как минимум два блока, начинающихся с FOURCC 'LIST': блок заголовка c FOURCC 'hdrl' и блок данных c FOURCC 'movi'. В заголовке 'hdrl' хранятся данные, определяющие форматы потоков, располагающихся в области 'movi', в том числе и разрешение изображения. Блок заголовка 'hdrl' содержит основной заголовок файла (MainAVIHeader), который предваряется заголовком c FOURCC 'avih'.

* FOURCC-четырехбуквенная зарезервированная абревиатура, двойное слово.


function TVideoStream.ParceAVI(var aWidth, aHeight: Word): Boolean;
const
  LIST = $5453494C; // FOURCC 'LIST'
  hdrl = $6C726468; // FOURCC 'hdrl'
  avih = $68697661; // FOURCC 'avih'
  OffData = 9;
var
  iValue: Long64Rec;
begin
  Result := False;
  repeat
    ReadBuffer(iValue, SizeOf(iValue));     // 'LIST'
    if (iValue.Lo = LIST) then begin
      ReadBuffer(iValue, SizeOf(iValue));   // 'hdrl' + 'avih'
      if (iValue.Lo = hdrl) and (iValue.Hi = avih) then begin
        Seek(OffData * SizeOf(DWord), soFromCurrent); // offset in MainAVIHeader
        ReadBuffer(iValue, SizeOf(iValue)); // Width & Height
        aWidth := iValue.Lo;
        aHeight := iValue.Hi;
        Result := True;
        Break;
      end;
    end;
  until Size - Position <= SizeOf(iValue);
end;

  Думаю что тут все понятно, единственное что нужно обьяснить, я не читаю весь заголовок MainAVIHeader. Т.к. его структура неизменна, я сразу перехожу на нужные поля по зараннее подсчитанному отступу OffData. Существует расширение AVI стандарта для поддержания файлов более 2 Гб, но оно MainAVIHeader не затрагивает.

Получение разрешения видеофайла MKV.

  Данные в медиаконтейнере MKV организованы в древовидном порядке, как и в контейнере MP4. Хотя везде в стандартах утверждается, что организация данных аналогична XML файлам с бинарными тегами. Наверное с какой стороны посмотреть. Особенно часто подчеркивается его гибкость, всеядность, расширяемость. Может так оно и есть с точки зрения проектировшика, а вот с точки зрения программиста решившего написать простенький парсер для получения разрешения видеофайла, этот контейнер — головная боль.
Информация в файлах формата MKV хранится в элементах EBML, в формате Big-Endian.

type
  EBML_ELEMENT = packed record
    ID: vint;
    size: vint;
    data: array[size] of Byte;
  end;

  Тип vint это беззнаковое целочисленное значение переменной длины, способ кодирования размера основан на формате кодировки UTF-8. Длина числа определяется ведущей единицей слева (со старшего разряда). Эта ведущая единица нужна только для определения размера числа и из самого числа должна быть удалена. А т.к. типом vint представлены и идентификатор и поле размера данных, функция определения ведущей единицы и удаления ее из числа должна быть применена дважды на каждом EBML элементе.

function GetEbmlSize(var Value: Byte): Byte;
begin
  Result := 0;
  if Value = 0 then Exit;
  if Value and $80 <> 0 then Result := 1 else
  if Value and $40 <> 0 then Result := 2 else
  if Value and $20 <> 0 then Result := 3 else
  if Value and $10 <> 0 then Result := 4 else
  if Value and $08 <> 0 then Result := 5 else
  if Value and $04 <> 0 then Result := 6 else
  if Value and $02 <> 0 then Result := 7 else
  if Value and $01 <> 0 then Result := 8;
  Value := Value and not ($80 shr Pred(Result));
end;

  Алгоритм функции довольно понятен, но для разных аргументов требует разное время работы, если ведущая единица старшая — требуется одна проверка, если младшая — уже восемь. Меньшее и стабильное число проверок обеспечивает функция, построенная на алгоритме бинарного поиска (деление отрезка пополам), она еще и немного быстрее.

function GetEbmlSize(var Value: Byte): Byte;
var
  Buff, Safe: Byte;
begin
  Result := 0;
  if Value = 0 then Exit
  else Inc(Result);
  Safe := Value;
  Buff := Safe and $F0;
  if Buff = 0 then begin
    Buff := Safe shl 4;
    Inc(Result, 4);
  end;
  Safe := Buff;
  Buff := Buff and $C0;
  if Buff = 0 then begin
    Buff := Safe shl 2;
    Inc(Result, 2);
  end;
  Buff := Buff and $80;
  if Buff = 0 then Inc(Result, 1);
  Value := Value and not ($80 shr coded(Result));
end;

Еще более быструю (более чем в 2 раза) функцию можно написать на inline assebler. Она быстрее не потому, что написана на ассемблере, а потому что использует специализированную команду i386 BSR для поиска ведущего старшего бита, ну и ассемблерные оптимизации немного ускоряют.

function GetEbmlSize(var Value: Byte): Byte; register;
asm
  PUSH    EBX
  MOV     EBX, EAX
  XOR     EAX, EAX
  MOV     AL, [EBX]
  BSR     CX, AX
  JZ      @loc_exit
  MOV     AX, $08
  SUB     AX, CX
  MOV     DL, $01
  SHL     DL, CL
  NOT     DL
  AND     [EBX], DL
@loc_exit:
  POP     EBX
end;

Быстродействие этой функции очень важно, т.к. на каждом элементе EBML она используется дважды, сначала для определения длины поля идентификатора ID, а затем для определения длины поля размера данных.

Длина идентификатора ID элемента может быть 1..4 байта, фиксированные 4 байта имеют элементы верхнего уровня (top-level, class D);
  • Class A IDs, base 0x8X, bytes 1
  • Class B IDs, base 0x4X, bytes 2
  • Class C IDs, base 0x2X, bytes 3
  • Class D IDs, base 0x1X, bytes 4
Длина поля size элемента может быть 1..8 байт, числа беззнаковые. Идентификатор и размер записаны в формате Big-Endian, и если для идентификатора в данном парсере это не важно (для поиска разрешения используются зараннее преобразованные EBML константы нужных элементов), то поле размера необходимо преобразовать функцией изменения эндианности. Поэтому к функциям изменения эндианности 2, 4 и 8 байтных чисел описаным в предыдущей статье, необходимо добавить функции преобразования 3, 5, 6, 7 байтных чисел. Вообще-то MKV файлы с таким размером полей размера мне не встречались, но в стандарте (EBML — basics) они предусмотрены и разбор поля размера в нем (2.1 Unsigned Integer Values of Variable Length (”vint“)) приводится для 3-х байтного числа.

Для 3-х байтового числа функция SwapEndian24 является доработанной функцией SwapEndian32:

function SwapEndian24(Value: DWord): DWord;
begin
  Result := ((Value and $FFFF0000) shr 16) or ((Value and $0000FFFF) shl 16);
  Result := ((Result and $FF00FF00) shr 8) or ((Result and $00FF00FF) shl 8);
  Result := Result shr $08;
end;

Полученный 32-битный результат просто сдвигается на байт вправо после преобразования. В ассемблерном варианте число сдвигается на байт влево до преобразования, что одно и тоже.

function SwapEndian24(Value: DWord): DWord; register;
asm
  SHL   EAX, $08
  BSWAP EAX
end;

Для 5, 6, 7-байтных чисел была написана одна универсальная функция на все три преобразования, где Base — кол-во байт конвертируемого числа:

function SwapEndian40_56(Value: Int64; Base: Byte): Int64;
var
  InBuff: Long64Rec absolute Value;
  OutBuff: Long64Rec absolute Result;
begin
  OutBuff.Lo := ((InBuff.Hi and $FFFF0000) shr 16) or ((InBuff.Hi and $0000FFFF) shl 16);
  OutBuff.Lo := ((OutBuff.Lo and $FF00FF00) shr 8) or ((OutBuff.Lo and $00FF00FF) shl 8);
  OutBuff.Hi := ((InBuff.Lo and $FFFF0000) shr 16) or ((InBuff.Lo and $0000FFFF) shl 16);
  OutBuff.Hi := ((OutBuff.Hi and $FF00FF00) shr 8) or ((OutBuff.Hi and $00FF00FF) shl 8);
  Result := Result shr (($08 - Base) shl 3);
end;

Полученный 64-битный результат просто сдвигается на 8, 16, 24 байт вправо после преобразования. В ассемблерном варианте число сдвигается влево до преобразования, здесь это требуется для оптимизации регистров.

function SwapEndian40_56(Value: Int64; Base: Byte): Int64; register;
asm
  MOV   ECX, $00000008
  SUB   ECX, EAX
  SHL   ECX, 3
  MOV   EDX, DWORD PTR [Value]
  MOV   EAX, DWORD PTR [Value+4]
  SHLD  EAX, EDX, CL
  SHL   EDX, CL
  BSWAP EDX
  BSWAP EAX
end;

  Теперь после знакомства с низкоуровневыми функциями можно рассмотреть и код самого парсера MKV. Контейнер MKV состоит из 2-х сегментов, Header и Segment. HEX-коды EBML элементов были подсмотрены в этом файле, сразу скажу список элементов не полон, но ориентироваться на него можно. Header мы уже отбросили определением типа контейнера. Должен остаться один Segment, так рекомендуют в стандарте. Но при этом не запрещают размещать и другие данные (расширяемость!). Поэтому вместо предположения, что элемент EBML_ID_SEGMENT лежит первым, мы его ищем. Чтобы упростить поиск, константы 4-х байтных идентификаторов EBML элементов представлены в Little-Endian, бит определения размера удален.

function TVideoStream.ParceMkv(var aWidth, aHeight: Word): Boolean;
const // consts in Little-Endian
  EBML_ID_SEGMENT = $67805308; // level 0, top-level container
  EBML_ID_TRACKS = $6BAE5406;  // level 1, top-level master
var
  iSize: Int64;
begin
  iSize := Size - Position;
  Result := MkvFindTopLevelD(EBML_ID_SEGMENT, iSize);
  if Result then begin
    Result := MkvFindTopLevelD(EBML_ID_TRACKS, iSize);
    if Result then begin
      Result := MkvFindVideoTrack(aWidth, aHeight);
    end;
  end;
end;

  Код последовательно проходит по Top-Level элементам EBML_ID_SEGMENT -> EBML_ID_TRACKS с помощью функции MkvFindTopLevelD. На входе в переменной iSize должна содержаться максимальная длина поискового блока. На выходе она будет содержать размер найденного элемента. А т.к уровни вложенные, то передача переменной последовательно от предшествующей функции к последующей позволяет сузить диаппазон адресов поиска и предотвратить выход функции поиска за пределы области при ошибочном считывании.

function TVideoStream.MkvFindTopLevelD(aID: DWord; var aSize: Int64): Boolean;
var
  iLimit: Int64;
  iSizeID: Byte;
  iBuffD: DWordRec;
  iBuffP: PDWordRec;
  iID: DWordRec absolute aID;
begin
  Result := False;
  iLimit := aSize + Position;
  repeat
    iBuffD.Body := 0;
    ReadBuffer(iBuffD.Bytes[0], SizeOf(iBuffD.Bytes[0]));
    iSizeID := GetEbmlSize(iBuffD.Bytes[0]);
    if iSizeID = 0 then Exit;
    if iSizeID = SizeOf(aID) then begin
      if iBuffD.Bytes[0] = iID.Bytes[0] then begin
        iBuffP := @iBuffD.Bytes[1];
        ReadBuffer(iBuffP^, iSizeID-1);
        Result := iBuffD.Body = aID;
        MkvGetSizeData(aSize);
        if Result then Break;
      end else begin
        Seek(iSizeID-1, soFromCurrent);
        MkvGetSizeData(aSize);
      end;
    end else begin
      if iSizeID > 1 then Seek(iSizeID-1, soFromCurrent);
      MkvGetSizeData(aSize);
    end;
    Seek(aSize, soFromCurrent);
  until Position >= iLimit;
end;

  Перед циклом устанавливаем iLimit — максимальный адрес поиска. На входе в цикл обнулим 4-х байтный буфер и читаем первый байт EBML идентификатора в младший байт буфера, т.к первый байт содержит размер идентификатора. Получаем размер в переменную iSizeID, одновременно удалив бит размера из младшего байта буфера. Если размер = 0, ошибка, выходим. Проверяем размер на размер идентификатора верхнего уровня, если не равен, пропускаем поле идентификатора, читаем размер данных и перемещаемся на следующий идентификатор. Если же размер равен размеру идентификатора верхнего уровня, сравниваем младшие байты буфера и искомого идентификатора. Если не равны, пропускаем поле идентификатора, читаем размер данных и перемещаемся на следующий идентификатор. Если равны, дочитываем идентификатор до конца и сравниваем искомый идентификатор уже полностью, итог сравнения в результат функции. Снова читаем размер чтобы перейти на начало данных, и если идентификатор найден выходим из цикла и функции, если нет перемещаемся на следующий идентификатор. При положительном результате выхода переменная aSize указывает на размер области более низкого уровня, позиция потока на ее начало. В коде широко применяется функция MkvGetSizeData, которую мы пока не рассматривали.

function TVideoStream.MkvGetSizeData(var aSize: Int64): Byte;
var
  BuffP: PLong64Rec;
  InBuff: Long64Rec;
  OutBuff: Long64Rec absolute aSize;
begin
  InBuff.Body := 0;
  ReadBuffer(InBuff.Bytes[0], SizeOf(InBuff.Bytes[0]));
  Result := GetEbmlSize(InBuff.Bytes[0]);
  if Result > 1 then begin
    BuffP := @InBuff.Bytes[1];
    ReadBuffer(BuffP^, Result-1);
  end;
  OutBuff.Body := 0;
  case Result of
    1: OutBuff.Body := InBuff.Body;
    2: OutBuff.Words[0] := SwapEndian16(InBuff.Words[0]);
    3: OutBuff.Lo := SwapEndian24(InBuff.Lo);
    4: OutBuff.Lo := SwapEndian32(InBuff.Lo);
    5, 6, 7: OutBuff.Body := SwapEndian40_56(InBuff.Body, Result);
    8: OutBuff.Body := SwapEndian64(InBuff.Body);
  end;
end;

  Начинается она так-же, очисткой 8-байтного буфера и чтением в младший байт буфера первого байта поля размера EBML элемента. GetEbmlSize получает кол-во байтов поля в результат функции и удаляет бит размера из младшего байта буфера. Если размер поля больше 1 дочитываем поле до конца, обнуляем выходной буфер (он и переменная aSize это одно и то-же). Затем в зависимости от кол-ва байт поля размера применяем соответствующую функцию изменения эндианности к выходному буферу. На выходе в переменной aSize содержимое поля размера EBML элемента в Little-Endian.
  Надо сказать что парсер был написан для одной узкоспециализированной цели — определение разрешения видеофайла, поэтому допускались некоторые упрощения. Так после нахождения элемента верхнего уровня EBML_ID_TRACKS для дальнейшего поска мне достаточно поисковой функции с поисковыми идентификаторами размером байт, это упрощенная функция поиска Top-Level идентификаторов.

function TVideoStream.MkvFindInternallD(aID: Byte; var aSize: Int64): Boolean;
var
  iLimit: Int64;
  iBuffB, iSizeID: Byte;
begin
  Result := False;
  iLimit := aSize + Position;
  repeat
    ReadBuffer(iBuffB, SizeOf(iBuffB));
    iSizeID := GetEbmlSize(iBuffB);
    if iSizeID = 0 then Exit;
    if iSizeID = SizeOf(aID) then begin
      Result := iBuffB = aID;
      MkvGetSizeData(aSize);
      if Result then Break;
    end else begin
      Seek(iSizeID-1, soFromCurrent);
      MkvGetSizeData(aSize);
    end;
    Seek(aSize, soFromCurrent);
  until Position >= iLimit;
end;

  Т.к это упрощенная функция, пояснения к MkvFindTopLevelD тут все обьяснят. Эта функция применяется как вспомогательная непосредственно для поиска разрешения видеофайла.

function TVideoStream.MkvFindVideoTrack(var aWidth, aHeight: Word; var aSize: Int64): Boolean;
const
  EBML_ID_TRACKENTRY = $2E;  // level 2, describes a track with all elements.
  EBML_ID_VIDEO = $60;       // level 3, video settings
  EBML_ID_PIXELWIDTH = $30;  // level 4, width of the encoded video frames in pixels
  EBML_ID_PIXELHEIGHT = $3A; // level 4, height of the encoded video frames in pixels
var
  iLimit: Int64;
  procedure MakeResolution(var iSize: Word);
  var
    BuffW: Word;
    BuffR: WordRec absolute BuffW;
  begin
    BuffW := 0;
    if aSize = SizeOf(Word) then begin
      ReadBuffer(BuffW, SizeOf(BuffW));
      iSize := SwapEndian16(BuffW);
    end else begin
      ReadBuffer(BuffR.Lo, SizeOf(BuffR.Lo));
      iSize := BuffW;
    end;
  end;
begin
  Result := False;
  iLimit := aSize + Position;
  repeat
    Result := MkvFindInternallD(EBML_ID_TRACKENTRY, aSize);
    if Result then begin
      iLimit := aSize + Position;
      Result := MkvFindInternallD(EBML_ID_VIDEO, aSize);
      if Result then begin
        iLimit := aSize + Position;
        Result := MkvFindInternallD(EBML_ID_PIXELWIDTH, aSize);
        if Result then begin
          MakeResolution(aWidth);
          aSize := iLimit - aSize - SizeOf(Word);
          Result := MkvFindInternallD(EBML_ID_PIXELHEIGHT, aSize);
          if Result then begin
            MakeResolution(aHeight);
            Break;
          end;
        end;
      end else Continue;
    end;
  until Position >= iLimit;
end;

  Тут все просто, функция проходит последовательно по EBML элементам EBML_ID_TRACKENTRY -> EBML_ID_VIDEO, последний и содержит требуемые элементы EBML_ID_PIXELWIDTH и EBML_ID_PIXELHEIGHT. Размер данных EBML_ID_VIDEO предварительно сохраняется и передается во вложенные элементы с корректировкой.

Резюме.

К стате приложен тестовый проект, от проекта из первой части он отличается только поддержкой всех 3-х поддерживаемых доработанным парсером форматов.
  • 0
  • 09 декабря 2019, 18:18
  • anakost
  • 1
Файлы в топике: Project.zip

Комментарии (1)

RSS свернуть / развернуть
Нашел ошибку, был невнимателен, поправил.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.