Почему я получаю ошибки "Неподдерживаемый формат", считывая потоки rtsp, закодированные H.264, с помощью Android MediaPlayer?

Я пытаюсь показать видео rtsp с кодировкой H.264 на устройстве Android. Поток поступает из малины Pi, используя vlc для кодирования /dev/video1, который является "Pi NoIR Camera Board".

vlc-wrapper -vvv v4l2:///dev/video1 --v4l2-width $WIDTH --v4l2-height $HEIGHT --v4l2-fps ${FPS}.0 --v4l2-chroma h264 --no-audio --no-osd --sout "#rtp{sdp=rtsp://:8000/pi.sdp}" :demux=h264 > /tmp/vlc-wrapper.log 2>&1

Я использую очень минимальный код Android прямо сейчас:

final MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDisplay(holder);
try {
  mediaPlayer.setDataSource(url);
  mediaPlayer.prepare();

и получить "Подготовить сбой: статус = 0x1" IOException. Когда я смотрю на журналы, я вижу строки типа

06-02 16:28:05.566 W/APacketSource(  316): Format:video 0 RTP/AVP 96  / MIME-Type:H264/90000
06-02 16:28:05.566 W/MyHandler(  316): Unsupported format. Ignoring track #1.
06-02 16:28:05.566 I/MyHandler(  316): SETUP(1) completed with result -1010 (Unknown error 1010)

поступающий из системного процесса. Grepping для этих сообщений указывает на источники libstagefright/rtsp и, похоже, означает, что вызов ASessionDescription::getDimensions в конструкторе APacketSource::APacketSource не работает. Это не похоже, что это должно происходить, потому что VLC, конечно, знает, какие размеры выводить:

[0x1c993a8] v4l2 demux debug: trying specified size 800x600
[0x1c993a8] v4l2 demux debug: Driver requires at most 262144 bytes to store a complete image
[0x1c993a8] v4l2 demux debug: Interlacing setting: progressive
[0x1c993a8] v4l2 demux debug: added new video es h264 800x600

Что, кажется, происходит, так это то, что ASessionDescription::getDimensions ищет атрибут framesize в (казалось бы, правильно сформированном) результатах DESCRIBE

06-02 16:28:05.566 I/MyHandler(  316): DESCRIBE completed with result 0 (Success)
06-02 16:28:05.566 I/ASessionDescription(  316): v=0
06-02 16:28:05.566 I/ASessionDescription(  316): o=- 15508012299902503225 15508012299902503225 IN IP4 pimple
06-02 16:28:05.566 I/ASessionDescription(  316): s=Unnamed
06-02 16:28:05.566 I/ASessionDescription(  316): i=N/A
06-02 16:28:05.566 I/ASessionDescription(  316): c=IN IP4 0.0.0.0
06-02 16:28:05.566 I/ASessionDescription(  316): t=0 0
06-02 16:28:05.566 I/ASessionDescription(  316): a=tool:vlc 2.0.3
06-02 16:28:05.566 I/ASessionDescription(  316): a=recvonly
06-02 16:28:05.566 I/ASessionDescription(  316): a=type:broadcast
06-02 16:28:05.566 I/ASessionDescription(  316): a=charset:UTF-8
06-02 16:28:05.566 I/ASessionDescription(  316): a=control:rtsp://192.168.1.35:8000/pi.sdp
06-02 16:28:05.566 I/ASessionDescription(  316): m=video 0 RTP/AVP 96
06-02 16:28:05.566 I/ASessionDescription(  316): b=RR:0
06-02 16:28:05.566 I/ASessionDescription(  316): a=rtpmap:96 H264/90000

Похоже, что это может быть ошибка Stagefright: он знает (или должен знать), что у него есть кодированный поток H.264, но, похоже, он ожидает атрибут H.263 framesize. Отсюда мои вопросы:

  • Я правильно читаю источники? Проблема в вызове ASessionDescription::getDimensions? (Действительно ли stagefright поддерживает потоки H.263?)
  • Или неправильный код Pi-side?
  • Или я просто пропустил один или два ключа в своем клиентском коде?

Обновление, 20140606:

MediaPlayer docs говорят, что -1010 MEDIA_ERROR_UNSUPPORTED: "Битовый поток соответствует соответствующему стандарту кодирования или спецификации файла, но медиа-инфраструктура не поддерживает эту функцию". Это заставляет меня задаться вопросом, является ли проблема "стандартной" проблемой прогрессивной загрузки. То есть Поддерживаемые форматы носителей говорит

Для видеоконтента, транслируемого через HTTP или RTSP [в] MPEG-4 [контейнер], атом moov должен предшествовать любым атомам mdat, но должен преуспеть в ftyp atom

в то время как большинство потоков помещают атом moov последним.

Я не совсем уверен, как это проверить!

  • Я не вижу атомов moov или ftyp в источнике vlc. (Мне сказали, что vlc просто потоковая передача здесь, что фактическое содержимое H264 выходит из драйвера камеры.)
  • Я не вижу атомов moov или ftyp в https://github.com/raspberrypi ветвях linux или userland. (Может быть, я просто готовлюсь к неправильным вещам.)
  • Когда у меня есть vlc, сохраните поток, я получаю файл mp4 с moov до mdat, но, конечно, vlc может выполнять некоторую перекодировку здесь.

Обновление, 20140610:

Игрок GPAC Osmo4 может отображать поток на планшете Android 4.3. Плохо (больше лаги, чем VLC на ноутбуке, и склонны к блокировкам), но он может отображать его.

Обновление, 20140616:

Когда я снова попытался повторно использовать источники VLC (без учета регистра и без слов), я нашел макросы FOURCC, определяющие атомы moov и ftyp в modules/mux/mp4.c, что быстро привело к --sout-mp4-faststart--no-sout-mp4-faststart) переключатели... которые не имеют никакого значения.

Итак, похоже, что на самом деле это не проблема с упорядочением атомов. Это полезно знать, если он закрывает целый класс тупиков, но он оставляет меня, ударяя головой о стену (которая, как всегда, наносит больше урона моей голове, чем стене) без подсказки.

Обновление, 20140702:

Я собрал VLC для Android, и он может отображать поток, созданный VLC на пике. Он помещает изображение в верхнем левом углу экрана; Я попробовал написать свою собственную шкуру для их .so, и не мог найти никаких "ручек", которые позволяли бы мне масштабировать поверхность или что-то еще. (Плюс к .apk пришел к 12M!)

Итак, я нашел соответствующие RFC и написал свой собственный клиент RTSP. Или попытался: я могу проанализировать SDP и создать достаточно допустимый RTSP, чтобы получить RTP и датаграммы RTCP, и я могу анализировать заголовки RTP и RTCP. Но даже несмотря на то, что SDP утверждает, что доставляет m = видео 0 RTP/AVP 96 и a = rtpmap: 96 H264/90000, MediaCodec не будет отображать видео на моей поверхности, независимо от того, какой из трех кодеков H264 на моем планшете я перехожу на MediaCodec.createByCodecName() и когда я смотрю на полезную нагрузку RTP, я не слишком удивлен: я не вижу шаблон синхронизации NAL в любом из пакетов.

Вместо этого все они начинаются с 21 9A __ 22 FF (обычно) или изредка 3C 81 9A __ 22 FF, где __ всегда всегда является четным числом, которое увеличивается на 2 каждого пакета. Я не признаю этот шаблон - не так ли?

Обновление, 20140711:

Оказывается, что пакеты H264 не должны начинаться с шаблона синхронизации NAL - это необходимо только там, где блоки NAL могут быть встроены в больший поток данных. Мои пакеты RTP находятся в формате RFC 6184.

Ответ 1

После потрясающего количества тупиков я могу показать поток H264 RTSP на Android SurfaceView. Этот ответ - это всего лишь ответ, потому что я все еще не могу ответить на мои первоначальные три вопроса, но даже полный ошибок и ярлыков, как это, мой 75K apk намного лучше, чем Vlc для Android или osmo4-плеер: -средняя задержка (по крайней мере, когда отправитель и получатель находятся на одном маршрутизаторе wifi!) и заполняет SurfaceView.

Несколько вынос, чтобы помочь любому, кто пытается сделать что-либо подобное:

  • Все входные буферы, которые вы передаете в MediaCodec.queueInputBuffer(), должны начинаться с синхронизации 00 00 01 шаблон.
  • Вы можете configure() и start() кодек сразу, но не ставьте в очередь никаких "нормальных" входных буферов, пока не увидите пакет SPS (NALU код 7) и PPS (NALU code 8). (Это могут быть не 0x67 и 0x68 - бит nal_ref_idc должен быть отличным от нуля, но необязательно равен 11. Fwiw, vlc, кажется, всегда дает мне 01.)
  • Передавайте пакеты SPS/PPS почти нормально - передайте BUFFER_FLAG_CODEC_CONFIG флаг queueInputBuffer(). В частности, не пытайтесь помещать их в буфер "csd-0", прикрепленный к MediaFormat!
  • Когда вы видите (а) пропущенный кадр (т.е. вы видите прыжок в порядковом номере RTP) не вызов codec.flush()! Просто пропустите частичный кадр и не ставьте очередь в буфер до следующего полного кадра.