Рендеринг видео битый поток MediaCodec H.264
Я реализую декодер с помощью MediaCodec
Java API для декодирования живого удаленного потока H.264. Я получаю данные в формате H.264 с нативного уровня с помощью обратного вызова (void OnRecvEncodedData(byte[] encodedData)
), декодировать и визуализировать на Surface
из TextureView
, Моя реализация завершена (извлечение закодированных потоков с использованием обратного вызова, декодирования и рендеринга и т. Д.). Вот мой класс декодера:
public class MediaCodecDecoder extends Thread implements MyFrameAvailableListener {
private static final boolean VERBOSE = true;
private static final String LOG_TAG = MediaCodecDecoder.class.getSimpleName();
private static final String VIDEO_FORMAT = "video/avc"; // h.264
private static final long mTimeoutUs = 10000l;
private MediaCodec mMediaCodec;
Surface mSurface;
volatile boolean m_bConfigured;
volatile boolean m_bRunning;
long startMs;
public MediaCodecDecoder() {
JniWrapper.SetFrameAvailableListener(this);
}
// this is my callback where I am receiving encoded streams from native layer
@Override
public void OnRecvEncodedData(byte[] encodedData) {
if(!m_bConfigured && bKeyFrame(encodedData)) {
Configure(mSurface, 240, 320, encodedData);
}
if(m_bConfigured) {
decodeData(encodedData);
}
}
public void SetSurface(Surface surface) {
if (mSurface == null) {
mSurface = surface;
}
}
public void Start() {
if(m_bRunning)
return;
m_bRunning = true;
start();
}
public void Stop() {
if(!m_bRunning)
return;
m_bRunning = false;
mMediaCodec.stop();
mMediaCodec.release();
}
private void Configure(Surface surface, int width, int height, byte[] csd0) {
if (m_bConfigured) {
Log.e(LOG_TAG, "Decoder is already configured");
return;
}
if (mSurface == null) {
Log.d(LOG_TAG, "Surface is not available/set yet.");
return;
}
MediaFormat format = MediaFormat.createVideoFormat(VIDEO_FORMAT, width, height);
format.setByteBuffer("csd-0", ByteBuffer.wrap(csd0));
try {
mMediaCodec = MediaCodec.createDecoderByType(VIDEO_FORMAT);
} catch (IOException e) {
Log.d(LOG_TAG, "Failed to create codec: " + e.getMessage());
}
startMs = System.currentTimeMillis();
mMediaCodec.configure(format, surface, null, 0);
if (VERBOSE) Log.d(LOG_TAG, "Decoder configured.");
mMediaCodec.start();
Log.d(LOG_TAG, "Decoder initialized.");
m_bConfigured = true;
}
@SuppressWarnings("deprecation")
private void decodeData(byte[] data) {
if (!m_bConfigured) {
Log.e(LOG_TAG, "Decoder is not configured yet.");
return;
}
int inIndex = mMediaCodec.dequeueInputBuffer(mTimeoutUs);
if (inIndex >= 0) {
ByteBuffer buffer;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
buffer = mMediaCodec.getInputBuffers()[inIndex];
buffer.clear();
} else {
buffer = mMediaCodec.getInputBuffer(inIndex);
}
if (buffer != null) {
buffer.put(data);
long presentationTimeUs = System.currentTimeMillis() - startMs;
mMediaCodec.queueInputBuffer(inIndex, 0, data.length, presentationTimeUs, 0);
}
}
}
private static boolean bKeyFrame(byte[] frameData) {
return ( ( (frameData[4] & 0xFF) & 0x0F) == 0x07);
}
@Override
public void run() {
try {
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while(m_bRunning) {
if(m_bConfigured) {
int outIndex = mMediaCodec.dequeueOutputBuffer(info, mTimeoutUs);
if(outIndex >= 0) {
mMediaCodec.releaseOutputBuffer(outIndex, true);
}
} else {
try {
Thread.sleep(10);
} catch (InterruptedException ignore) {
}
}
}
} finally {
Stop();
}
}
}
Теперь проблема в том, что потоки декодируются и отображаются на поверхности, но видео нечеткое. Кажется, что кадры сломаны, а сцена искажена / загрязнена. Движение разбито и фрагменты квадратной формы везде (мне очень жаль, потому что у меня нет скриншота сейчас).
О моих потоках - его кодирование H.264 и состоит только из I-кадров и P-кадров (B-кадра нет). Каждый кадр I имеет SPS + PPS + payload
состав. Цветовой формат, используемый во время кодирования (с использованием FFMPEG на собственном уровне), является планировщиком YUV420. Длина данных, отправленных из собственного слоя, в порядке (ширина * высота * (3 / 2)).
В течение configure()
Я просто установил csd-0
значение с рамкой SPS. Кадр, использованный для конфигурации, был I-кадром (SPS + PPS + полезная нагрузка) - префикс был кадром SPS, поэтому я думаю, что конфигурация прошла успешно. Обратите внимание, что я не установил csd-1
значение с кадром PPS (это проблема?).
Каждый кадр имеет предшествующие стартовые коды (0x00 0x00 0x00 0x01
) как для p-кадра, так и для I-кадра (для I-кадра стартовый код присутствует как перед SPS, так и PPS-кадром).
Кроме того, я устанавливаю метку времени презентации как System.currrentTimeMillis() - startTime
для каждого кадра, который увеличивается порядок для каждого нового кадра. Я думаю, что это не должно вызывать никаких проблем (поправьте меня, если я ошибаюсь).
Мое устройство - Nexus 5 от Google с Android версии 4.4.4, а чипсет - Qualcomm MSM8974 Snapdragon 800. Я использую Surface
для декодирования, поэтому я думаю, что не должно быть каких-либо специфических для устройства проблем с несоответствием формата цвета.
Я также могу предоставить TextureView
код, если это необходимо.
В чем может быть причина моего неправильного декодирования / рендеринга? Заранее спасибо!
РЕДАКТИРОВАТЬ 1
Я попытался вручную передать специфичные для кодека данные (байты SPS и PPS) во время настройки. Но это ничего не изменило:(
byte[] sps = {0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x40, 0x0c, (byte) 0xda, 0x0f, 0x0a, 0x68, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x07, (byte) 0xa3, (byte) 0xc5, 0x0a, (byte) 0xa8};
format.setByteBuffer("csd-0", ByteBuffer.wrap(sps));
byte[] pps = {0x00, 0x00, 0x00, 0x01, 0x68, (byte) 0xef, 0x04, (byte) 0xf2, 0x00, 0x00};
format.setByteBuffer("csd-1", ByteBuffer.wrap(pps));
Я тоже пытался обрезать стартовые коды (0x00, 0x00, 0x00, 0x01
) но прогресса нет!
РЕДАКТИРОВАТЬ 2
Я попытался с аппаратным ускорением {{TextureView}}, как это упоминается в официальной документации (хотя я не нашел никакого кода ускорения H/W в примере проекта MediaCodec-textureView). Но до сих пор нет прогресса. Теперь я прокомментировал фрагмент кода H/W ускорения.
РЕДАКТИРОВАТЬ 3
Скриншоты теперь доступны:
РЕДАКТИРОВАТЬ 4
Для дальнейшего пояснения, это мой формат шестнадцатеричного потока I-кадра в кодировке H.264:
00 00 00 01 67 4d 40 0c da 0f 0a 68 40 00 00 03 00 40 00 00 07 a3 c5 0a a8 00 00 00 01 68 ef 04 f2 00 00 01 06 05 ff ff 69 dc 45 e9 bd e6 d9 48 b7 96 2c d8 20 d9 23 ee ef 78 32 36 34 20 2d 20 63 6f 72 65 20 31 34 36 20 2d 20 48 2e 32 36 34 2f 4d 50 45 47 2d 34 20 41 56 43 20 63 6f 64 65 63 20 2d 20 43 6f 70 79 6c 65 66 74 20 32 30 30 33 2d 32 30 31 35 20 2d 20 68 74 74 70 3a 2f 2f 77 77 77 2e 76 69 64 65 6f 6c 61 6e 2e 6f 72 67 2f 78 32 36 34 2e 68 74 6d 6c 20 2d 20 6f 70 74 69 6f 6e 73 3a 20 63 61 62 61 63 3d 31 20 72 65 66 3d 31 20 64 65 62 6c 6f 63 6b 3d 31 3a 30 3a 30 20 61 6e 61 6c 79 73 65 3d 30 78 31 3a 30 78 31 20 6d 65 3d 68 65 78 20 73 75 62 6d 65 3d 30 20 70 73 79 3d 31 20 70 73 79 5f 72 64 3d 31 2e 30 30 3a 30 2e 30 30 20 6d 69 78 65 64 5f 72 65 66 3d 30 20 6d 65 5f 72 61 6e 67 65 3d 31 36 20 63 68 72 6f 6d 61 5f 6d 65 3d 31 20 74 72 65 6c 6c 69 73 3d 30 20 38 78 38 64 63 74
И это P-рамка:
00 00 00 01 41 9a 26 22 df 76 4b b2 ef cf 57 ac 5b b6 3b 68 b9 87 b2 71 a5 9b 61 3c 93 47 bc 79 c5 ab 0f 87 34 f6 40 6a cd 80 03 b1 a2 c2 4e 08 13 cd 4e 3c 62 3e 44 0a e8 97 80 ec 81 3f 31 7c f1 29 f1 43 a0 c0 a9 0a 74 62 c7 62 74 da c3 94 f5 19 23 ff 4b 9c c1 69 55 54 2f 62 f0 5e 64 7f 18 3f 58 73 af 93 6e 92 06 fd 9f a1 1a 80 cf 86 71 24 7d f7 56 2c c1 57 cf ba 05 17 77 18 f1 8b 3c 33 40 18 30 1f b0 19 23 44 ec 91 c4 bd 80 65 4a 46 b3 1e 53 5d 6d a3 f0 b5 50 3a 93 ba 81 71 f3 09 98 41 43 ba 5f a1 0d 41 a3 7b c3 fd eb 15 89 75 66 a9 ee 3a 9c 1b c1 aa f8 58 10 88 0c 79 77 ff 7d 15 28 eb 12 a7 1b 76 36 aa 84 e1 3e 63 cf a9 a3 cf 4a 2d c2 33 18 91 30 f7 3c 9c 56 f5 4c 12 6c 4b 12 1f c5 ec 5a 98 8c 12 75 eb fd 98 a4 fb 7f 80 5d 28 f9 ef 43 a4 0a ca 25 75 19 6b f7 14 7b 76 af e9 8f 7d 79 fa 9d 9a 63 de 1f be fa 6c 65 ba 5f 9d b0 b0 f4 71 cb e2 ea d6 dc c6 55 98 1b cd 55 d9 eb 9c 75 fc 9d ec
Я почти уверен в правильности моего потока, поскольку я успешно рендерил с использованием ffmpeg
декодирование и GLSurfaceview
с OpenGLES 2.0
,
1 ответ
Я взял дамп H.264 как из нативного слоя, так и из Java-уровня и обнаружил, что дамп нативного уровня воспроизводится идеально, но дамп Java-слоя воспроизводится так же прерывисто, как и декодированный поток. Проблема заключалась в том, что во время передачи закодированных потоков с нативного уровня на Java закодированный поток не был передан должным образом (поврежден), и это было из-за моей ошибочной реализации (извините, кто следил за этим потоком за это неудобство).
Более того, я передавал полезную нагрузку I-кадра только декодеру, что приводило к нарушению рендеринга. Сейчас я прохожу полный блок NAL (SPS + PPS + полезная нагрузка), и теперь все в порядке:)