title: Empty frames after using MediaCodec + MediaMuxer on Android
body: <p>I'm trying to create .mp4 video from .png images in my app the next way:
  decode Bitmap from .png file -> convert RGB bitmap to YUV420SP -> pass coverted bitmap to codec's input and queue it -> dequeue output frame -> write it with muxer to .mp4 file -> repeat with other image -> close and release muxer. The output .mp4 file cannot be played in Android but in VLC player it plays the right length (16 seconds at all, 4 sec with 4 images) but frames are empty, only VLC logo shown. I can't figure out what's wrong for few days, this last code is mix of few stackoverflow answers on similar questions, but I haven't found enough information to make it works.
  Below the snippets</p>
  
  <p>Here where all starts:</p>
  
  <pre><code>encoder = new AvcEncoder(this);
  Bitmap bitmap = BitmapFactory.decodeFile(path);
  byte[] data = getNV21(bitmap.getWidth(), bitmap.getHeight(), bitmap);
  encoder.offerEncoder(data);
  ...
  muxer.stop();
  muxer.release();
  </code></pre>
  
  <p>This method is called from AvcEncoder when output appeared from codec:</p>
  
  <pre><code>@Override
  public void frameReceived(byte[] outData, int i, int length) {
      ByteBuffer buf = ByteBuffer.wrap(outData);
  
      bufferInfo.set(0, outData.length, duration * frameNo++ * 1000000, MediaCodec.BUFFER_FLAG_SYNC_FRAME);
      muxer.writeSampleData(videoTrackIndex, buf, bufferInfo);
  }
  </code></pre>
  
  <p>Here converting snippets:</p>
  
  <pre><code>byte[] getNV21(int inputWidth, int inputHeight, Bitmap scaled) {
  
      int[] argb = new int[inputWidth * inputHeight];
  
      scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
  
      byte[] yuv = new byte[inputWidth * inputHeight * 3 / 2];
      encodeYUV420SP(yuv, argb, inputWidth, inputHeight);
  
      scaled.recycle();
  
      return yuv;
  }
  
  void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
      final int frameSize = width * height;
  
      int yIndex = 0;
      int uvIndex = frameSize;
  
      int a, R, G, B, Y, U, V;
      int index = 0;
      for (int j = 0; j &lt; height; j++) {
          for (int i = 0; i &lt; width; i++) {
  
              a = (argb[index] &amp; 0xff000000) &gt;&gt; 24; // a is not used obviously
              R = (argb[index] &amp; 0xff0000) &gt;&gt; 16;
              G = (argb[index] &amp; 0xff00) &gt;&gt; 8;
              B = (argb[index] &amp; 0xff) &gt;&gt; 0;
  
              // well known RGB to YUV algorithm
              Y = ((66 * R + 129 * G + 25 * B + 128) &gt;&gt; 8) + 16;
              U = ((-38 * R - 74 * G + 112 * B + 128) &gt;&gt; 8) + 128;
              V = ((112 * R - 94 * G - 18 * B + 128) &gt;&gt; 8) + 128;
  
              // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2
              //    meaning for every 4 Y pixels there are 1 V and 1 U.  Note the sampling is every other
              //    pixel AND every other scanline.
              yuv420sp[yIndex++] = (byte) ((Y &lt; 0) ? 0 : ((Y &gt; 255) ? 255 : Y));
              if (j % 2 == 0 &amp;&amp; index % 2 == 0) {
                  yuv420sp[uvIndex++] = (byte) ((V &lt; 0) ? 0 : ((V &gt; 255) ? 255 : V));
                  yuv420sp[uvIndex++] = (byte) ((U &lt; 0) ? 0 : ((U &gt; 255) ? 255 : U));
              }
  
              index++;
          }
      }
  }
  </code></pre>
  
  <p>And <em>AvcEncoder</em> class:</p>
  
  <pre><code>public class AvcEncoder {
  
  private EncodedFrameListener frameListener;
  private MediaCodec mediaCodec;
  
  private byte[] sps;
  private byte[] pps;
  //private ParameterSetsListener parameterSetsListener;
  
  public AvcEncoder(EncodedFrameListener frameListener) {
      this.frameListener = frameListener;
      MediaFormat mediaFormat;
      mediaFormat = MediaFormat.createVideoFormat("video/avc", 720, 720);
      mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
      mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
      mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
      mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
      mediaCodec = MediaCodec.createEncoderByType("video/avc");
      mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
      mediaCodec.start();
  
  }
  
  
  public MediaFormat getOutputFormat(){
      return mediaCodec.getOutputFormat();
  }
  public void close() throws IOException {
      mediaCodec.stop();
      mediaCodec.release();
  }
  
  public void offerEncoder(byte[] input) {
      try {
          ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
          ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
          int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
          if (inputBufferIndex &gt;= 0) {
              ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
              inputBuffer.clear();
              inputBuffer.put(input);
              mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
          }
  
          MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
          int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, -1);
          while (outputBufferIndex &gt;= 0) {
              ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
              byte[] outData = new byte[bufferInfo.size];
              outputBuffer.get(outData);
              if (sps != null &amp;&amp; pps != null) {
                  ByteBuffer frameBuffer = ByteBuffer.wrap(outData);
                  frameBuffer.putInt(bufferInfo.size - 4);
                  frameListener.frameReceived(outData, 0, outData.length);
              } else {
                  ByteBuffer spsPpsBuffer = ByteBuffer.wrap(outData);
                  if (spsPpsBuffer.getInt() == 0x00000001) {
                      System.out.println("parsing sps/pps");
                  } else {
                      System.out.println("something is amiss?");
                  }
                  int ppsIndex = 0;
                  while(!(spsPpsBuffer.get() == 0x00 &amp;&amp; spsPpsBuffer.get() == 0x00 &amp;&amp; spsPpsBuffer.get() == 0x00 &amp;&amp; spsPpsBuffer.get() == 0x01)) {
  
                  }
                  ppsIndex = spsPpsBuffer.position();
                  sps = new byte[ppsIndex - 8];
                  System.arraycopy(outData, 4, sps, 0, sps.length);
                  pps = new byte[outData.length - ppsIndex];
                  System.arraycopy(outData, ppsIndex, pps, 0, pps.length);
                  /*if (null != parameterSetsListener) {
                      parameterSetsListener.avcParametersSetsEstablished(sps, pps);
                  }*/
              }
              mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
              outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
          }
      } catch (Throwable t) {
          t.printStackTrace();
      }
  
  }
  }
  </code></pre>