public void writeHead() throws IOException {
    if (null == streams) {
      throw new IllegalArgumentException("No Streams Found");
    }

    ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(1024);

    int dataSize = 0;

    // flv header
    buffer.writeByte('F');
    buffer.writeByte('L');
    buffer.writeByte('V');
    buffer.writeByte(0x01); // version
    buffer.writeByte(0x05); // audio & video
    buffer.writeInt(0x09); // header size

    buffer.writeInt(0x00); // first tag size

    // metadata
    Map<String, Object> map = new LinkedHashMap<String, Object>();
    map.put("duration", 0);
    for (int i = 0; i < streams.length; i++) {
      AVStream av = (AVStream) streams[i];

      if (av.getFormat() instanceof VideoFormat) {
        if (av.getWidth() > 0) {
          map.put("width", av.getWidth());
        }
        if (av.getHeight() > 0) {
          map.put("heigth", av.getHeight());
        }
        if (av.getFrameRate() > 0) {
          map.put("videoframerate", av.getFrameRate());
        }
      } else if (av.getFormat() instanceof AudioFormat) {
        if (av.getSampleRate() > 0) {
          map.put("audiosamplerate", av.getSampleRate());
        }
        if (av.getNumChannels() > 0) {
          map.put("audiochannels", av.getNumChannels());
        }
      }
    }

    ChannelBuffer onMetaData = ChannelBuffers.dynamicBuffer();
    Amf0Value.encode(onMetaData, "onMetaData");
    Amf0Value.encode(onMetaData, map);

    dataSize = onMetaData.readableBytes();
    buffer.writeByte(0x18); // script type
    buffer.writeMedium(dataSize);
    buffer.writeInt(0); // timestamp
    buffer.writeMedium(0); // stream id
    buffer.writeBytes(onMetaData);

    buffer.writeInt(dataSize + 11); // pre tag size

    buffer.readBytes(out, buffer.readableBytes());

    // stream config(s)
    for (AVStream stream : streams) {
      tryWriteHeader(stream);
    }
  }
  private void tryWriteHeader(AVStream stream) throws IOException {
    if (stream.getStreamIndex() >= writeHeaders.length) {
      logger.error("Fail Add Stream#{}", stream.getStreamIndex());
      return;
    } else if (writeHeaders[stream.getStreamIndex()]) {
      logger.debug("has writen headers");
      return;
    }

    int dataSize = 0;
    ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(1024);
    AVStreamExtra extra = stream.getExtra();
    if (extra instanceof H264Extra) {
      H264Extra h264 = (H264Extra) extra;
      ByteBuffer profile = h264.readProfile();
      if (null == profile || profile.remaining() != 3) {
        logger.warn("H264.profile is NOT FOUND");
        return;
      }

      ChannelBuffer avc = ChannelBuffers.buffer(128);
      avc.writeByte(0x17); // key frame + avc
      avc.writeByte(0x00); // avc sequence header
      avc.writeMedium(0x00); // Composition time offset

      avc.writeByte(0x01);
      avc.writeBytes(profile);
      avc.writeByte(0xFF); // 4 byte for nal header length

      // sps(s)
      ByteBuffer sps = h264.readSps();
      avc.writeByte(0xE0 | 0x01);
      avc.writeShort(sps.remaining());
      avc.writeBytes(sps);

      // pps(s)
      ByteBuffer pps = h264.readPps();
      avc.writeByte(0x01);
      avc.writeShort(pps.remaining());
      avc.writeBytes(pps);

      dataSize = avc.readableBytes();
      buffer.writeByte(0x09); // video type
      buffer.writeMedium(dataSize);
      buffer.writeInt(0); // timestamp
      buffer.writeMedium(0); // stream id
      buffer.writeBytes(avc);

      buffer.writeInt(11 + dataSize);
      buffer.readBytes(out, buffer.readableBytes());
      writeHeaders[stream.getStreamIndex()] = true;
    } else if (extra instanceof AACExtra) {
      AACExtra aac = (AACExtra) extra;

      ByteBuffer aacHeader = ByteBuffer.allocate(4);
      aacHeader.put((byte) 0xAF); // aac, 44100, 2 channels, stereo
      aacHeader.put((byte) 0x00); // aac header

      int objectType = aac.getObjectType();
      int sampleRateIndex = aac.getSampleRateIndex();
      int numChannels = aac.getNumChannels();

      BitWriter w = new BitWriter(aacHeader);
      w.writeNBit(objectType, 5);
      w.writeNBit(sampleRateIndex, 4);
      w.writeNBit(numChannels, 4);
      w.writeNBit(0, 1); // frame length - 1024 samples
      w.writeNBit(0, 1); // does not depend on core coder
      w.writeNBit(0, 1); // is not extension
      w.flush();
      aacHeader.flip();

      dataSize = aacHeader.remaining();
      buffer.writeByte(0x08);
      buffer.writeMedium(dataSize); // data size
      buffer.writeInt(0x00); // timestamp
      buffer.writeMedium(0x00); // stream id

      buffer.writeBytes(aacHeader); // data

      buffer.writeInt(11 + dataSize); // pre tag size
      buffer.readBytes(out, buffer.readableBytes());
      writeHeaders[stream.getStreamIndex()] = true;
    } else if (null == stream.getExtra()) {
      logger.warn("no stream extra found {}", stream.getFormat());
    } else {
      logger.warn("unsupported {}", stream);
    }
  }