Как кодировать растровые изображения в видео с помощью MediaCodec?
Я хотел бы кодировать набор растровых изображений, которые у меня есть в h264. Возможно ли это с помощью MediaEncoder? Я написал код, чтобы сделать это, но выход не может быть воспроизведен в любом медиаплеере, который я пробовал. Вот некоторые из кода, который я в основном заимствовал из других источников, которые я нашел в Stackoverflow.
mMediaCodec = MediaCodec.createEncoderByType("video/avc");
mMediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240);
mMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
mMediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
mMediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
mMediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
mMediaCodec.configure(mMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mMediaCodec.start();
mInputBuffers = mMediaCodec.getInputBuffers();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); // image is the bitmap
byte[] input = byteArrayOutputStream.toByteArray();
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = mInputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
}
Что мне нужно настроить?
Ответы
Ответ 1
Вывод MediaCodec
является исходным потоком исходного элемента H.264. Я обнаружил, что проигрыватель Totem для Linux способен воспроизводить их.
Что вы действительно хотите сделать, так это преобразовать вывод в файл .mp4. (Обновление:). Android 4.3 (API 18) представил класс MediaMuxer, который обеспечивает способ преобразования необработанные данные (плюс дополнительный аудиопоток) в файл .mp4.
Макет данных в ByteBuffer
, как и Android 4.3, зависит от устройства. (На самом деле, не все устройства поддерживают COLOR_FormatYUV420Planar
- некоторые предпочитают полуплоский вариант.) Я не могу сказать вам точный макет, не зная вашего устройства, но могу сказать вам, что он хочет несжатые данные, поэтому прохождение сжатые данные PNG не будут работать.
(Обновление:). Android 4.3 также позволяет вводить поверхностные данные в кодеры MediaCodec
, поэтому все, что вы можете сделать с помощью OpenGL ES, можно записать. Например, см. Пример EncodeAndMuxTest здесь.
Ответ 2
- Выход кодеров "raw" h264, поэтому вы можете установить расширение имени файла на "h264" и воспроизвести его с помощью mplayer, т.е.
mplayer ./your_output.h264
- Еще одна вещь: вы говорите кодировщику, что кадр будет в цветовом формате COLOR_FormatYUV420Planar, но похоже, что вы даете ему PNG-контент, поэтому выходной файл, вероятно, будет содержать цветной беспорядок. Я думаю, вы должны преобразовать PNG в yuv420 (с этим, например, https://code.google.com/p/libyuv/), прежде чем подавать его в кодировщик.
Ответ 3
Я использовал следующие шаги для преобразования моих растровых изображений в видеофайл.
Шаг 1: Подготовка
Я подготовил кодировщик вот так. Я использую MediaMuxer для создания файла mp4.
private void prepareEncoder() {
try {
mBufferInfo = new MediaCodec.BufferInfo();
mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate());
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
}else{
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
}
//2130708361, 2135033992, 21
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, SAMPLE_RATE, 1);
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
mediaCodecForAudio = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
mediaCodecForAudio.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodecForAudio.start();
try {
String outputPath = new File(Environment.getExternalStorageDirectory(),
"test.mp4").toString();
mediaMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (IOException ioe) {
throw new RuntimeException("MediaMuxer creation failed", ioe);
}
} catch (IOException e) {
e.printStackTrace();
}
}
Шаг 2: Буферизация
Я создал runnable для буферизации.
private void bufferEncoder() {
runnable = new Runnable() {
@Override
public void run() {
prepareEncoder();
try {
while (mRunning) {
encode();
}
encode();
} finally {
release();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
Шаг 3: Кодирование
Это самая важная часть, которую вы пропустили. В этой части я подготовил входной буфер перед выходом. Когда входные буферы поставлены в очередь, выходные буферы готовы к кодированию.
public void encode() {
while (true) {
if (!mRunning) {
break;
}
int inputBufIndex = mediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
long ptsUsec = computePresentationTime(generateIndex);
if (inputBufIndex >= 0) {
Bitmap image = loadBitmapFromView(captureImageView);
image = Bitmap.createScaledBitmap(image, WIDTH, HEIGHT, false);
byte[] input = getNV21(WIDTH, HEIGHT, image);
final ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufIndex);
inputBuffer.clear();
inputBuffer.put(input);
mediaCodec.queueInputBuffer(inputBufIndex, 0, input.length, ptsUsec, 0);
generateIndex++;
}
int encoderStatus = mediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
// no output available yet
Log.d("CODEC", "no output from encoder available");
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// not expected for an encoder
MediaFormat newFormat = mediaCodec.getOutputFormat();
mTrackIndex = mediaMuxer.addTrack(newFormat);
mediaMuxer.start();
} else if (encoderStatus < 0) {
Log.i("CODEC", "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
} else if (mBufferInfo.size != 0) {
ByteBuffer encodedData = mediaCodec.getOutputBuffer(encoderStatus);
if (encodedData == null) {
Log.i("CODEC", "encoderOutputBuffer " + encoderStatus + " was null");
} else {
encodedData.position(mBufferInfo.offset);
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
mediaMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
mediaCodec.releaseOutputBuffer(encoderStatus, false);
}
}
}
}
}
Шаг 4: Освобождение
Наконец, если мы закончили кодирование, отпустите мультиплексор и кодировщик.
private void release() {
if (mediaCodec != null) {
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
Log.i("CODEC", "RELEASE CODEC");
}
if (mediaMuxer != null) {
mediaMuxer.stop();
mediaMuxer.release();
mediaMuxer = null;
Log.i("CODEC", "RELEASE MUXER");
}
}
Надеюсь, это поможет вам.