2013-06-13 32 views
21

Mi piacerebbe codificare un set di Bitmap che ho in un h264. È possibile tramite MediaEncoder? Ho scritto del codice per farlo, ma l'output non può essere riprodotto con nessun lettore multimediale che ho provato. Ecco alcuni dei codici che ho preso in prestito principalmente da altre fonti che ho trovato su Stackoverflow.Come codificare Bitmap in un video utilizzando MediaCodec?

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); 
} 

Cosa devo regolare?

risposta

12

L'output di MediaCodec è un flusso elementare H.264 non elaborato. Ho scoperto che il lettore multimediale Totem per Linux è in grado di riprodurli.

Quello che si vuole veramente fare è convertire l'output in un file .mp4. (Aggiornamento:) Android 4.3 (API 18) ha introdotto la classe MediaMuxer, che fornisce un modo per convertire i dati non elaborati (più un flusso audio facoltativo) in un file .mp4.

Il layout dei dati nello ByteBuffer è, a partire da Android 4.3, dipendente dal dispositivo. (In effetti, non tutti i dispositivi supportano COLOR_FormatYUV420Planar - alcuni preferiscono una variante semiplanare.) Non posso dirti il ​​layout esatto senza conoscere il tuo dispositivo, ma posso dirti che vuole dati non compressi, quindi passare in PNG compresso i dati non funzioneranno

(Aggiornamento:) Android 4.3 consente anche l'input di superficie agli encoder MediaCodec, quindi è possibile registrare qualsiasi cosa è possibile renderizzare con OpenGL ES. Per un esempio, consultare l'esempio EncodeAndMuxTest here.

+0

Potrebbe essere possibile spostare l'output su un PC e codificarlo su mp4? C'è un modo per determinare universalmente il formato corretto per l'input per tutti i dispositivi Android compatibili? – BVB

+1

Credo che si possa fare la conversione fuori dispositivo, ma non ho un programma specifico da consigliare. A partire da Android 4.2, è possibile interrogare il codec per i formati che supporta, ma non tutti i dispositivi trattano il formato allo stesso modo (ad esempio alcuni hanno restrizioni di allineamento all'inizio dei dati chroma). – fadden

+1

Android 4.3 ha aggiunto l'input di Surface e la conversione di 'MediaMuxer' in .mp4. Risposta aggiornata – fadden

4
  1. Encoder uscita è h264 "raw", in modo da poter impostare il nome del file estensione "H264" e riprodurlo con mplayer, cioè mplayer ./your_output.h264
  2. Ancora una cosa: voi dice encoder telaio sarà a colori COLOR_FormatYUV420Planar formato ma sembra che tu gli dia il contenuto PNG, quindi il file di output probabilmente conterrà confusione di colori. Penso che dovresti convertire PNG in yuv420 (con questo, ad esempio, https://code.google.com/p/libyuv/) prima di inviarlo al codificatore.
+0

Sono supportati altri formati? Cosa dovrei usare se volessi inserire dei bitmap bitmap grezzi? – BVB

+1

È possibile trovare i formati supportati nella documentazione degli sviluppatori http://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities.html. Ma prima di configurare il codificatore per utilizzare un determinato formato colore è necessario iterare tutti i codec e verificarne le funzionalità (utilizzando questo http://developer.android.com/reference/android/media/MediaCodecList.html). – user2399321

+1

Se l'encoder supporta COLOR_Format32bitARGB8888 o COLOR_Format32bitBGRA8888 è possibile utilizzare il contenuto PNG (non ho mai incontrato tali dispositivi). Altrimenti l'immagine dovrebbe essere convertita da PNG in una sorta di yuv. Se la conversione non è un processo critico nel tempo, puoi scrivere il tuo codice java ma dovresti occuparti del contenuto di yuv (qui è il punto di partenza - http://www.fourcc.org/yuv.php). – user2399321

4

Ho utilizzato i seguenti passaggi per convertire i miei bitmap in file video.

Punto 1: Preparazione

ho preparato encoder come questo. Uso MediaMuxer per creare un file 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(); 
    } 
} 

Fase 2: Buffer

ho creato eseguibile per il buffering.

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(); 
    } 

Fase 3: Encoding

Questa è la parte più importante che avete perso. In questa parte, ho preparato il buffer di input prima dell'output. Quando i buffer di input sono in coda, i buffer di output sono pronti per la codifica.

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); 
        } 
       } 
      } 
     } 
    } 

Fase 4: Rilascio

Infine, se abbiamo finito codifica quindi rilasciare il muxer ed encoder.

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"); 
     } 
    } 

Spero che questo ti sia d'aiuto.

+0

Grazie per la risposta dettagliata! – BVB