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

Я пытаюсь показать rtsp-видео в формате H.264 на устройстве Android. Поток исходит от Raspberry Pi, используя vlc для кодирования /dev/video1, который является «платой камеры Pi NoIR».

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)

исходящие из системного процесса. Поиск этих сообщений указывает на источники 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. Отсюда мои вопросы:

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

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

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

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

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

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

  • Я не вижу атомов moov или ftyp в источнике vlc. (Мне сказали, что здесь vlc просто транслируется; что фактический контент H264 выходит из драйвера камеры.)
  • Я не вижу атомов moov или ftyp в https://github.com/raspberrypi linux или пользовательских ветках. (Возможно, я просто ищу не те вещи, хотя.)
  • Когда 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=video 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.


person Jon Shemitz    schedule 03.06.2014    source источник
comment
можете ли вы читать тот же поток с другим приложением, например. VLC?   -  person Alex Cohn    schedule 07.06.2014
comment
Ха, я удивлен, что не упомянул об этом. Да, VLC может читать поток... но, конечно, VLC готов открыть второй поток, чтобы найти атом moov, а Android — нет. (Извините, ссылка на это есть на машине на работе.)   -  person Jon Shemitz    schedule 07.06.2014
comment
bytechunk.net/mobile_progressive_playback/index.php   -  person Jon Shemitz    schedule 11.06.2014
comment
На самом деле, я думал, вы пробовали воспроизводить VLC на Android…   -  person Alex Cohn    schedule 11.06.2014
comment
Мне удалось кодировать видео для воспроизведения на мобильных устройствах, используя комбинацию imagemagic (я использую его для объединения изображений) и avconv (manpages.ubuntu.com/manpages/precise/man1/avconv.1.html) Оба прекрасно работают в Linux, а поскольку RPi основан на Linux, проблем быть не должно. чтобы вы начали работать. Просто предложение. Стоит изучить, так как у меня не было большого успеха с любыми другими инструментами.   -  person jamesc    schedule 18.06.2014


Ответы (1)


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

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

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