Я хочу захватить каждый кадр из видео, чтобы внести некоторые изменения перед рендерингом на устройстве Android, например Nexus 10. Как я знаю, Android использует аппаратное обеспечение для декодирования и рендеринга кадра в конкретном устройстве, поэтому я должен получить данные кадра из GraphicBuffer, и перед рендерингом данные будут в формате YUV.
Также я пишу статический метод в AwesomePlayer.cpp, чтобы реализовать захват данных фрейма / модифицировать фрейм / записать их обратно в GraphicBuffer для рендеринга.
Вот мой демонстрационный код
static void handleFrame(MediaBuffer *buffer) {
sp<GraphicBuffer> buf = buffer->graphicBuffer();
size_t width = buf->getWidth();
size_t height = buf->getHeight();
size_t ySize = buffer->range_length();
size_t uvSize = width * height / 2;
uint8_t *yBuffer = (uint8_t *)malloc(ySize + 1);
uint8_t *uvBuffer = (uint8_t *)malloc(uvSize + 1);
memset(yBuffer, 0, ySize + 1);
memset(uvBuffer, 0, uvSize + 1);
int const *private_handle = buf->handle->data;
void *yAddr = NULL;
void *uvAddr = NULL;
buf->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, &yAddr);
uvAddr = mmap(0, uvSize, PROT_READ | PROT_WRITE, MAP_SHARED, *(private_handle + 1));
if(yAddr != NULL && uvAddr != NULL) {
//memcpy data from graphic buffer
memcpy(yBuffer, yAddr, ySize);
memcpy(uvBuffer, uvAddr, uvSize);
//modify the YUV data
//memcpy data into graphic buffer
memcpy(yAddr, yBuffer, ySize);
memcpy(uvAddr, uvBuffer, uvSize);
}
munmap(uvAddr, uvSize);
buf->unlock();
free(yBuffer);
free(uvBuffer);
}
Я напечатал метку времени для функции memcpy и понял, что memcpy от GraphicBuffer занимает гораздо больше времени, чем данные memcpy в GraphicBuffer. Возьмите видео с разрешением 1920×1080, например, memcpy от GraphicBuffer занимает около 30 мс, это неприемлемо для обычного воспроизведения видео.
Я понятия не имею, почему это занимает так много времени, может быть, он копирует данные из буфера GPU, но скопировать данные в GraphicBuffer выглядит нормально.
Может ли кто-нибудь еще, кто знаком с аппаратным декодированием в Android, взглянуть на эту проблему?
Спасибо большое.
Обновить:
Я обнаружил, что мне не нужно использовать GraphicBuffer для получения данных YUV, я просто использовал аппаратное декодирование источника видео и сохранение данных YUV в памяти, чтобы я мог напрямую получать данные YUV из памяти, это очень быстро.
На самом деле вы можете найти подобное решение в исходном коде AOSP или в приложении с открытым исходным кодом для отображения видео. Я просто выделяю буферы памяти, а не графические буферы, а затем использую аппаратный декодер.
Пример кода в AOSP: frameworks / av / cmds / stagefright / SimplePlayer.cpp
ссылка на сайт: https://github.com/xdtianyu/android-4.2_r1/tree/master/frameworks/av/cmds/stagefright
Скорее всего, путь данных (a.k.a. databus) от вашего процессора к графической памяти оптимизирован. Путь от графической памяти к процессору не может быть оптимизирован. Оптимизация может включать в себя различную скорость внутренней шины данных, кэш 1 или 2 уровня и состояния ожидания.
Электроника (аппаратная часть) установила максимальную скорость передачи данных из графической памяти в ваш ЦП. Память ЦП, вероятно, медленнее, чем ваша графическая память, поэтому могут быть состояния ожидания, чтобы графическая память соответствовала более медленной скорости памяти ЦП.
Другая проблема — все устройства, разделяющие шину данных. Представьте себе общую магистраль между городами. Для оптимизации трафика разрешено движение только в одном направлении. Светофоры или люди, следите за движением. Чтобы перейти от города А к городу С, нужно подождать, пока сигналы светофора или директор, очистить оставшийся трафик и дать приоритет маршруту от города А к городу С. В аппаратном плане это называется Автобусный арбитраж.
На большинстве платформ ЦП передает данные между регистрами и памятью ЦП. Это необходимо для чтения и записи ваших переменных в вашей программе. Медленный маршрут передачи данных заключается в том, что ЦП считывает память в регистр, а затем записывает в графическую память. Более эффективным методом является передача данных без использования процессора. Может существовать устройство DMA (прямой доступ к памяти), которое может передавать данные без использования процессора. Вы указываете ему источник и целевые области памяти, а затем запускаете его. Он будет передавать данные без использования процессора.
К сожалению, DMA должен совместно использовать шину данных с процессором. Это означает, что передача данных будет замедлена любыми запросами шины данных ЦП. Это все равно будет быстрее, чем использование ЦП для передачи данных, поскольку DMA может передавать данные, пока ЦП выполняет инструкции, не требующие шины данных.
Резюме
Ваша передача памяти может быть медленной, если у вас нет устройства DMA. С DMA или без него, шина данных используется несколькими устройствами, и трафик обрабатывается произвольно. Это устанавливает максимальную общую скорость передачи данных. Скорости передачи данных микросхем памяти также могут способствовать скорости передачи данных. Аппаратно, есть ограничение скорости.
Оптимизации
1. Используйте DMA, если это возможно.
2. Если вы используете только процессор, сделайте так, чтобы процессор передавал как можно больше фрагментов.
Это означает использование инструкций специально для копирования памяти.
3. Если ваш процессор не имеет специальных инструкций по копированию, перенесите, используя размер слова процессора.
Если процессор имеет 32-битные слова, передайте 4 байта за раз с 1 словом вместо использования 4 8-битных копий.
4. Уменьшите требования процессора и прерывания во время передачи.
Приостановить любые приложения; отключить прерывания, если это возможно.
5. Разделите усилия: пусть одно ядро передает данные, в то время как другое ядро выполняет вашу программу.
6. Потоки на одном ядре могут фактически замедлить передачу, так как операционная система вмешивается из-за планирования. Переключение потоков требует времени, которое увеличивает время передачи.
Других решений пока нет …