LiteCoding

Заметки о программировании

Архив по тэгу ‘libav’

libav: как хранится YUV420 в AVFrame

без комментариев

Давненько не было заметок на тему программирования (да и заметок вообще), но за все это время не было ничего особенно интересного, чем бы я мог поделиться с вами, мои читатели. Сейчас я ковыряюсь под капотом libav, поэтому в этой заметке речь пойдет об одной конкретной проблеме. Если вы не в курсе, что такое libav, то про эту чудесную библиотеку можно почитать тут. Для особо нетерпеливых скажу, что это именно то, что выполняет основную часть работы в ffmpeg.

У меня появилась задача декодировать кадр из видеопотока и сохранить его в оперативной памяти в YUV 4:2:0 (PixelFormat.PIX_FMT_YUV420P, как он поименован в libav). И если вопрос декодирования решается довольно быстро и легко (тут есть очень неплохой учебник по написанию видеоплеера с использованием libav, а тут — обновленные примеры из этого учебника для последней версии библиотеки), то попытки разобраться, как данные хранятся в AVFrame, долгое время не давали результата.

Нас интересуют прежде всего поля AVFrame.data и AVFrame.linesize. Первое — массив указателей на участки памяти с данными, а второе — массив с длинами строки данных. Последнее нужно пояснить отдельно — памяти выделяется больше, чем нужно для хранения данных, т.к. она может использоваться кодеком для более эффективного кодирования/декодирования. Т.е. AVFrame.data содержит как полезные данные, так и мусор (в лучшем случае — лишние нулевые байты). При этом данные разделены по каналам: Y-компоненты хранятся в памяти, на которую указывает data[0], U- и V-компоненты — в памяти по data[1] и data[2] соответственно. В моем случае для кадра 704×288 AVFrame.linesize был следующим: {736, 368, 368, 0, 0, 0, 0, 0}.

С Y-каналом никаких проблем возникнуть не могло: там находились 288 (height) строк длиной по 704 (width) байта, и каждая строка была выровнена по границе 736 байт (AVFrame.linesize[0]). А вот попытки прочитать подобным образом U- и V-каналы приводили к выходу за пределы массива данных. Как оказалось, в data[1] и data[2] данные хранятся следующим образом: 144 (height / 2) строки длиной по 352 байта (width / 2), выровненные по границе 368 байт (AVFrame.linesize[1] и AVFrame.linesize[2]).

И, наконец, алгоритм чтения на псевдокоде:

AVFrame frame;
/*...*/
for(int i = 0; i < frame.height; i++) {
    readY(frame.data[0], i * frame.linesize[0], frame.width);	
}
		
for(int i = 0; i < frame.height / 2; i++) {
    readU(frame.data[1], i * frame.linesize[1], frame.width / 2);
    readV(frame.data[2], i * frame.linesize[2], frame.width / 2);
}

И в итоге получаем YUV 4:2:0 фрейм, записанный в непрерывный участок памяти.

Written by Дмитрий Воробьев

Август 3rd, 2012 at 11:22