package net.ossrs.sea; import android.media.MediaCodec; import android.media.MediaFormat; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; /** * Created by winlin on 5/2/15. * Updated by leoma on 4/1/16. * to POST the h.264/avc annexb frame to SRS over RTMP. * @remark we must start a worker thread to send data to server. * @see android.media.MediaMuxer https://developer.android.com/reference/android/media/MediaMuxer.html * * Usage: * muxer = new SrsRtmp("rtmp://ossrs.net/live/sea"); * muxer.start(); * * MediaFormat aformat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, asample_rate, achannel); * // setup the aformat for audio. * atrack = muxer.addTrack(aformat); * * MediaFormat vformat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, vsize.width, vsize.height); * // setup the vformat for video. * vtrack = muxer.addTrack(vformat); * * // encode the video frame from camera by h.264 codec to es and bi, * // where es is the h.264 ES(element stream). * ByteBuffer es, MediaCodec.BufferInfo bi; * muxer.writeSampleData(vtrack, es, bi); * * // encode the audio frame from microphone by aac codec to es and bi, * // where es is the aac ES(element stream). * ByteBuffer es, MediaCodec.BufferInfo bi; * muxer.writeSampleData(atrack, es, bi); * * muxer.stop(); * muxer.release(); */ public class SrsRtmp { private boolean connected; private SrsRtmpPublisher publisher; private Thread worker; private Looper looper; private Handler handler; private SrsFlv flv; private boolean sequenceHeaderOk; private SrsFlvFrame videoSequenceHeader; private SrsFlvFrame audioSequenceHeader; // use cache queue to ensure audio and video monotonically increase. private ArrayList cache; private int nb_videos; private int nb_audios; private static final int VIDEO_TRACK = 100; private static final int AUDIO_TRACK = 101; private static final String TAG = "SrsMuxer"; /** * constructor. * @param path the rtmp url to post to. */ public SrsRtmp(SrsRtmpPublisher p) { nb_videos = 0; nb_audios = 0; sequenceHeaderOk = false; connected = false; flv = new SrsFlv(); cache = new ArrayList(); publisher = p; } /** * print the size of bytes in bb * @param bb the bytes to print. * @param size the total size of bytes to print. */ public static void srs_print_bytes(String tag, ByteBuffer bb, int size) { StringBuilder sb = new StringBuilder(); int i = 0; int bytes_in_line = 16; int max = bb.remaining(); for (i = 0; i < size && i < max; i++) { sb.append(String.format("0x%s ", Integer.toHexString(bb.get(i) & 0xFF))); if (((i + 1) % bytes_in_line) == 0) { Log.i(tag, String.format("%03d-%03d: %s", i / bytes_in_line * bytes_in_line, i, sb.toString())); sb = new StringBuilder(); } } if (sb.length() > 0) { Log.i(tag, String.format("%03d-%03d: %s", size / bytes_in_line * bytes_in_line, i - 1, sb.toString())); } } public static void srs_print_bytes(String tag, byte[] bb, int size) { StringBuilder sb = new StringBuilder(); int i = 0; int bytes_in_line = 16; int max = bb.length; for (i = 0; i < size && i < max; i++) { sb.append(String.format("0x%s ", Integer.toHexString(bb[i] & 0xFF))); if (((i + 1) % bytes_in_line) == 0) { Log.i(tag, String.format("%03d-%03d: %s", i / bytes_in_line * bytes_in_line, i, sb.toString())); sb = new StringBuilder(); } } if (sb.length() > 0) { Log.i(tag, String.format("%03d-%03d: %s", size / bytes_in_line * bytes_in_line, i - 1, sb.toString())); } } /** * Adds a track with the specified format. * @param format The media format for the track. * @return The track index for this newly added track. */ public int addTrack(MediaFormat format) { if (format.getString(MediaFormat.KEY_MIME) == MediaFormat.MIMETYPE_VIDEO_AVC) { flv.setVideoTrack(format); return VIDEO_TRACK; } flv.setAudioTrack(format); return AUDIO_TRACK; } /** * start to the remote SRS for remux. */ public void start() throws IOException { worker = new Thread(new Runnable() { @Override public void run() { try { cycle(); } catch (IOException e) { Log.i(TAG, "worker: thread exception."); e.printStackTrace(); } } }); worker.start(); } /** * Make sure you call this when you're done to free up any resources * instead of relying on the garbage collector to do this for you at * some point in the future. */ public void release() { try { stop(); } catch (IllegalStateException e) { // Ignore illegal state. } } /** * stop the muxer, disconnect RTMP connection from SRS. */ public void stop() throws IllegalStateException { clearCache(); if (worker == null || publisher == null) { throw new IllegalStateException("Not init!"); } if (publisher != null) { publisher.closeStream(); publisher.shutdown(); connected = false; } if (looper != null) { looper.quit(); } if (worker != null) { worker.interrupt(); try { worker.join(); } catch (InterruptedException e) { e.printStackTrace(); worker.interrupt(); } worker = null; publisher = null; } Log.i(TAG, String.format("worker: muxer closed, url=%s", publisher.getRtmpUrl())); } /** * send the annexb frame to SRS over RTMP. * @param trackIndex The track index for this sample. * @param byteBuf The encoded sample. * @param bufferInfo The buffer information related to this sample. */ public void writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo) throws Exception { //Log.i(TAG, String.format("dumps the %s stream %dB, pts=%d", (trackIndex == VIDEO_TRACK) ? "Vdieo" : "Audio", bufferInfo.size, bufferInfo.presentationTimeUs / 1000)); //SrsRtmp.srs_print_bytes(TAG, byteBuf, bufferInfo.size); if (bufferInfo.offset > 0) { Log.w(TAG, String.format("encoded frame %dB, offset=%d pts=%dms", bufferInfo.size, bufferInfo.offset, bufferInfo.presentationTimeUs / 1000 )); } if (VIDEO_TRACK == trackIndex) { flv.writeVideoSample(byteBuf, bufferInfo); } else { flv.writeAudioSample(byteBuf, bufferInfo); } } private void disconnect() throws IllegalStateException { clearCache(); if (publisher != null) { publisher.closeStream(); publisher.shutdown(); publisher = null; connected = false; } Log.i(TAG, "worker: disconnect SRS ok."); } private void clearCache() { nb_videos = 0; nb_audios = 0; cache.clear(); sequenceHeaderOk = false; } private void connect() throws IllegalStateException, IOException { if (!connected) { Log.i(TAG, String.format("worker: connecting to RTMP server by url=%s\n", publisher.getRtmpUrl())); publisher.connect(); publisher.publish("live"); Log.i(TAG, String.format("worker: connect to RTMP server by url=%s\n", publisher.getRtmpUrl())); connected = true; } clearCache(); } private void cycle() throws IOException { // create the handler. Looper.prepare(); looper = Looper.myLooper(); handler = new Handler(looper) { @Override public void handleMessage(Message msg) { if (msg.what != SrsMessageType.FLV) { Log.w(TAG, String.format("worker: drop unkown message, what=%d", msg.what)); return; } SrsFlvFrame frame = (SrsFlvFrame)msg.obj; // only reconnect when got keyframe. try { // only connect when got keyframe. if (frame.is_keyframe()) { connect(); } } catch (IOException e) { Thread.getDefaultUncaughtExceptionHandler().uncaughtException(worker, e); } try { // when sequence header required, // adjust the dts by the current frame and sent it. if (!sequenceHeaderOk) { if (videoSequenceHeader != null) { videoSequenceHeader.dts = frame.dts; } if (audioSequenceHeader != null) { audioSequenceHeader.dts = frame.dts; } sendFlvTag(audioSequenceHeader); sendFlvTag(videoSequenceHeader); sequenceHeaderOk = true; } // try to send, igore when not connected. if (sequenceHeaderOk) { sendFlvTag(frame); } // cache the sequence header. if (frame.type == SrsCodecFlvTag.Video && frame.avc_aac_type == SrsCodecVideoAVCType.SequenceHeader) { videoSequenceHeader = frame; } else if (frame.type == SrsCodecFlvTag.Audio && frame.avc_aac_type == 0) { audioSequenceHeader = frame; } } catch (IOException e) { e.printStackTrace(); Log.e(TAG, String.format("worker: send flv tag failed, e=%s", e.getMessage())); disconnect(); } } }; flv.setHandler(handler); Looper.loop(); } private void sendFlvTag(SrsFlvFrame frame) throws IOException { if (frame == null) { return; } if (frame.tag.size <= 0) { return; } if (frame.is_video()) { nb_videos++; } else if (frame.is_audio()) { nb_audios++; } cache.add(frame); // always keep one audio and one videos in cache. if (nb_videos > 1 && nb_audios > 1) { sendCachedFrames(); } } private void sendCachedFrames() throws IllegalStateException, IOException { Collections.sort(cache, new Comparator() { @Override public int compare(SrsFlvFrame lhs, SrsFlvFrame rhs) { return lhs.dts - rhs.dts; } }); while (nb_videos > 1 && nb_audios > 1) { SrsFlvFrame frame = cache.remove(0); if (frame.is_video()) { nb_videos--; if (publisher != null) { publisher.publishVideoData(frame.tag.frame.array(), frame.dts); } } else if (frame.is_audio()) { nb_audios--; if (publisher != null) { publisher.publishAudioData(frame.tag.frame.array(), frame.dts); } } if (frame.is_keyframe()) { Log.i(TAG, String.format("worker: send frame type=%d, dts=%d, size=%dB", frame.type, frame.dts, frame.tag.size)); } } } // E.4.3.1 VIDEODATA // Frame Type UB [4] // Type of video frame. The following values are defined: // 1 = key frame (for AVC, a seekable frame) // 2 = inter frame (for AVC, a non-seekable frame) // 3 = disposable inter frame (H.263 only) // 4 = generated key frame (reserved for server use only) // 5 = video info/command frame class SrsCodecVideoAVCFrame { // set to the zero to reserved, for array map. public final static int Reserved = 0; public final static int Reserved1 = 6; public final static int KeyFrame = 1; public final static int InterFrame = 2; public final static int DisposableInterFrame = 3; public final static int GeneratedKeyFrame = 4; public final static int VideoInfoFrame = 5; } // AVCPacketType IF CodecID == 7 UI8 // The following values are defined: // 0 = AVC sequence header // 1 = AVC NALU // 2 = AVC end of sequence (lower level NALU sequence ender is // not required or supported) class SrsCodecVideoAVCType { // set to the max value to reserved, for array map. public final static int Reserved = 3; public final static int SequenceHeader = 0; public final static int NALU = 1; public final static int SequenceHeaderEOF = 2; } /** * E.4.1 FLV Tag, page 75 */ class SrsCodecFlvTag { // set to the zero to reserved, for array map. public final static int Reserved = 0; // 8 = audio public final static int Audio = 8; // 9 = video public final static int Video = 9; // 18 = script data public final static int Script = 18; }; // E.4.3.1 VIDEODATA // CodecID UB [4] // Codec Identifier. The following values are defined: // 2 = Sorenson H.263 // 3 = Screen video // 4 = On2 VP6 // 5 = On2 VP6 with alpha channel // 6 = Screen video version 2 // 7 = AVC class SrsCodecVideo { // set to the zero to reserved, for array map. public final static int Reserved = 0; public final static int Reserved1 = 1; public final static int Reserved2 = 9; // for user to disable video, for example, use pure audio hls. public final static int Disabled = 8; public final static int SorensonH263 = 2; public final static int ScreenVideo = 3; public final static int On2VP6 = 4; public final static int On2VP6WithAlphaChannel = 5; public final static int ScreenVideoVersion2 = 6; public final static int AVC = 7; } /** * the aac object type, for RTMP sequence header * for AudioSpecificConfig, @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 33 * for audioObjectType, @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23 */ class SrsAacObjectType { public final static int Reserved = 0; // Table 1.1 – Audio Object Type definition // @see @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23 public final static int AacMain = 1; public final static int AacLC = 2; public final static int AacSSR = 3; // AAC HE = LC+SBR public final static int AacHE = 5; // AAC HEv2 = LC+SBR+PS public final static int AacHEV2 = 29; } /** * the aac profile, for ADTS(HLS/TS) * @see https://github.com/simple-rtmp-server/srs/issues/310 */ class SrsAacProfile { public final static int Reserved = 3; // @see 7.1 Profiles, aac-iso-13818-7.pdf, page 40 public final static int Main = 0; public final static int LC = 1; public final static int SSR = 2; } /** * the FLV/RTMP supported audio sample rate. * Sampling rate. The following values are defined: * 0 = 5.5 kHz = 5512 Hz * 1 = 11 kHz = 11025 Hz * 2 = 22 kHz = 22050 Hz * 3 = 44 kHz = 44100 Hz */ class SrsCodecAudioSampleRate { // set to the max value to reserved, for array map. public final static int Reserved = 4; public final static int R5512 = 0; public final static int R11025 = 1; public final static int R22050 = 2; public final static int R44100 = 3; } /** * the type of message to process. */ class SrsMessageType { public final static int FLV = 0x100; } /** * Table 7-1 – NAL unit type codes, syntax element categories, and NAL unit type classes * H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 83. */ class SrsAvcNaluType { // Unspecified public final static int Reserved = 0; // Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp( ) public final static int NonIDR = 1; // Coded slice data partition A slice_data_partition_a_layer_rbsp( ) public final static int DataPartitionA = 2; // Coded slice data partition B slice_data_partition_b_layer_rbsp( ) public final static int DataPartitionB = 3; // Coded slice data partition C slice_data_partition_c_layer_rbsp( ) public final static int DataPartitionC = 4; // Coded slice of an IDR picture slice_layer_without_partitioning_rbsp( ) public final static int IDR = 5; // Supplemental enhancement information (SEI) sei_rbsp( ) public final static int SEI = 6; // Sequence parameter set seq_parameter_set_rbsp( ) public final static int SPS = 7; // Picture parameter set pic_parameter_set_rbsp( ) public final static int PPS = 8; // Access unit delimiter access_unit_delimiter_rbsp( ) public final static int AccessUnitDelimiter = 9; // End of sequence end_of_seq_rbsp( ) public final static int EOSequence = 10; // End of stream end_of_stream_rbsp( ) public final static int EOStream = 11; // Filler data filler_data_rbsp( ) public final static int FilterData = 12; // Sequence parameter set extension seq_parameter_set_extension_rbsp( ) public final static int SPSExt = 13; // Prefix NAL unit prefix_nal_unit_rbsp( ) public final static int PrefixNALU = 14; // Subset sequence parameter set subset_seq_parameter_set_rbsp( ) public final static int SubsetSPS = 15; // Coded slice of an auxiliary coded picture without partitioning slice_layer_without_partitioning_rbsp( ) public final static int LayerWithoutPartition = 19; // Coded slice extension slice_layer_extension_rbsp( ) public final static int CodedSliceExt = 20; } /** * utils functions from srs. */ public class SrsUtils { private final static String TAG = "SrsMuxer"; public boolean srs_bytes_equals(byte[] a, byte[]b) { if ((a == null || b == null) && (a != null || b != null)) { return false; } if (a.length != b.length) { return false; } for (int i = 0; i < a.length && i < b.length; i++) { if (a[i] != b[i]) { return false; } } return true; } public SrsAnnexbSearch srs_avc_startswith_annexb(ByteBuffer bb, MediaCodec.BufferInfo bi) { SrsAnnexbSearch as = new SrsAnnexbSearch(); as.match = false; int pos = bb.position(); while (pos < bi.size - 3) { // not match. if (bb.get(pos) != 0x00 || bb.get(pos + 1) != 0x00) { break; } // match N[00] 00 00 01, where N>=0 if (bb.get(pos + 2) == 0x01) { as.match = true; as.nb_start_code = pos + 3 - bb.position(); break; } pos++; } return as; } public boolean srs_aac_startswith_adts(ByteBuffer bb, MediaCodec.BufferInfo bi) { int pos = bb.position(); if (bi.size - pos < 2) { return false; } // matched 12bits 0xFFF, // @remark, we must cast the 0xff to char to compare. if (bb.get(pos) != (byte)0xff || (byte)(bb.get(pos + 1) & 0xf0) != (byte)0xf0) { return false; } return true; } } /** * the search result for annexb. */ class SrsAnnexbSearch { public int nb_start_code = 0; public boolean match = false; } /** * the demuxed tag frame. */ class SrsFlvFrameBytes { public ByteBuffer frame; public int size; } /** * the muxed flv frame. */ class SrsFlvFrame { // the tag bytes. public SrsFlvFrameBytes tag; // the codec type for audio/aac and video/avc for instance. public int avc_aac_type; // the frame type, keyframe or not. public int frame_type; // the tag type, audio, video or data. public int type; // the dts in ms, tbn is 1000. public int dts; public boolean is_keyframe() { return type == SrsCodecFlvTag.Video && frame_type == SrsCodecVideoAVCFrame.KeyFrame; } public boolean is_video() { return type == SrsCodecFlvTag.Video; } public boolean is_audio() { return type == SrsCodecFlvTag.Audio; } } /** * the raw h.264 stream, in annexb. */ class SrsRawH264Stream { private SrsUtils utils; private final static String TAG = "SrsMuxer"; public SrsRawH264Stream() { utils = new SrsUtils(); } public boolean is_sps(SrsFlvFrameBytes frame) { if (frame.size < 1) { return false; } // 5bits, 7.3.1 NAL unit syntax, // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame int nal_unit_type = (int)(frame.frame.get(0) & 0x1f); return nal_unit_type == SrsAvcNaluType.SPS; } public boolean is_pps(SrsFlvFrameBytes frame) { if (frame.size < 1) { return false; } // 5bits, 7.3.1 NAL unit syntax, // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame int nal_unit_type = (int)(frame.frame.get(0) & 0x1f); return nal_unit_type == SrsAvcNaluType.PPS; } public SrsFlvFrameBytes mux_ibp_frame(SrsFlvFrameBytes frame) { SrsFlvFrameBytes nalu_header = new SrsFlvFrameBytes(); nalu_header.size = 4; nalu_header.frame = ByteBuffer.allocate(nalu_header.size); // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 // lengthSizeMinusOne, or NAL_unit_length, always use 4bytes size int NAL_unit_length = frame.size; // mux the avc NALU in "ISO Base Media File Format" // from H.264-AVC-ISO_IEC_14496-15.pdf, page 20 // NALUnitLength nalu_header.frame.putInt(NAL_unit_length); // reset the buffer. nalu_header.frame.rewind(); //Log.i(TAG, String.format("mux ibp frame %dB", frame.size)); //SrsRtmp.srs_print_bytes(TAG, nalu_header.frame, 16); return nalu_header; } public void mux_sequence_header(byte[] sps, byte[] pps, int dts, int pts, ArrayList frames) { // 5bytes sps/pps header: // configurationVersion, AVCProfileIndication, profile_compatibility, // AVCLevelIndication, lengthSizeMinusOne // 3bytes size of sps: // numOfSequenceParameterSets, sequenceParameterSetLength(2B) // Nbytes of sps. // sequenceParameterSetNALUnit // 3bytes size of pps: // numOfPictureParameterSets, pictureParameterSetLength // Nbytes of pps: // pictureParameterSetNALUnit // decode the SPS: // @see: 7.3.2.1.1, H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 62 if (true) { SrsFlvFrameBytes hdr = new SrsFlvFrameBytes(); hdr.size = 5; hdr.frame = ByteBuffer.allocate(hdr.size); // @see: Annex A Profiles and levels, H.264-AVC-ISO_IEC_14496-10.pdf, page 205 // Baseline profile profile_idc is 66(0x42). // Main profile profile_idc is 77(0x4d). // Extended profile profile_idc is 88(0x58). byte profile_idc = sps[1]; //u_int8_t constraint_set = frame[2]; byte level_idc = sps[3]; // generate the sps/pps header // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 // configurationVersion hdr.frame.put((byte)0x01); // AVCProfileIndication hdr.frame.put(profile_idc); // profile_compatibility hdr.frame.put((byte)0x00); // AVCLevelIndication hdr.frame.put(level_idc); // lengthSizeMinusOne, or NAL_unit_length, always use 4bytes size, // so we always set it to 0x03. hdr.frame.put((byte)0x03); // reset the buffer. hdr.frame.rewind(); frames.add(hdr); } // sps if (true) { SrsFlvFrameBytes sps_hdr = new SrsFlvFrameBytes(); sps_hdr.size = 3; sps_hdr.frame = ByteBuffer.allocate(sps_hdr.size); // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 // numOfSequenceParameterSets, always 1 sps_hdr.frame.put((byte) 0x01); // sequenceParameterSetLength sps_hdr.frame.putShort((short) sps.length); sps_hdr.frame.rewind(); frames.add(sps_hdr); // sequenceParameterSetNALUnit SrsFlvFrameBytes sps_bb = new SrsFlvFrameBytes(); sps_bb.size = sps.length; sps_bb.frame = ByteBuffer.wrap(sps); frames.add(sps_bb); } // pps if (true) { SrsFlvFrameBytes pps_hdr = new SrsFlvFrameBytes(); pps_hdr.size = 3; pps_hdr.frame = ByteBuffer.allocate(pps_hdr.size); // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 // numOfPictureParameterSets, always 1 pps_hdr.frame.put((byte) 0x01); // pictureParameterSetLength pps_hdr.frame.putShort((short) pps.length); pps_hdr.frame.rewind(); frames.add(pps_hdr); // pictureParameterSetNALUnit SrsFlvFrameBytes pps_bb = new SrsFlvFrameBytes(); pps_bb.size = pps.length; pps_bb.frame = ByteBuffer.wrap(pps); frames.add(pps_bb); } } public SrsFlvFrameBytes mux_avc2flv(ArrayList frames, int frame_type, int avc_packet_type, int dts, int pts) { SrsFlvFrameBytes flv_tag = new SrsFlvFrameBytes(); // for h264 in RTMP video payload, there is 5bytes header: // 1bytes, FrameType | CodecID // 1bytes, AVCPacketType // 3bytes, CompositionTime, the cts. // @see: E.4.3 Video Tags, video_file_format_spec_v10_1.pdf, page 78 flv_tag.size = 5; for (int i = 0; i < frames.size(); i++) { SrsFlvFrameBytes frame = frames.get(i); flv_tag.size += frame.size; } flv_tag.frame = ByteBuffer.allocate(flv_tag.size); // @see: E.4.3 Video Tags, video_file_format_spec_v10_1.pdf, page 78 // Frame Type, Type of video frame. // CodecID, Codec Identifier. // set the rtmp header flv_tag.frame.put((byte)((frame_type << 4) | SrsCodecVideo.AVC)); // AVCPacketType flv_tag.frame.put((byte)avc_packet_type); // CompositionTime // pts = dts + cts, or // cts = pts - dts. // where cts is the header in rtmp video packet payload header. int cts = pts - dts; flv_tag.frame.put((byte)(cts >> 16)); flv_tag.frame.put((byte)(cts >> 8)); flv_tag.frame.put((byte)cts); // h.264 raw data. for (int i = 0; i < frames.size(); i++) { SrsFlvFrameBytes frame = frames.get(i); byte[] frame_bytes = new byte[frame.size]; frame.frame.get(frame_bytes); flv_tag.frame.put(frame_bytes); } // reset the buffer. flv_tag.frame.rewind(); //Log.i(TAG, String.format("flv tag muxed, %dB", flv_tag.size)); //SrsRtmp.srs_print_bytes(TAG, flv_tag.frame, 128); return flv_tag; } public SrsFlvFrameBytes annexb_demux(ByteBuffer bb, MediaCodec.BufferInfo bi) throws Exception { SrsFlvFrameBytes tbb = new SrsFlvFrameBytes(); while (bb.position() < bi.size) { // each frame must prefixed by annexb format. // about annexb, @see H.264-AVC-ISO_IEC_14496-10.pdf, page 211. SrsAnnexbSearch tbbsc = utils.srs_avc_startswith_annexb(bb, bi); if (!tbbsc.match || tbbsc.nb_start_code < 3) { Log.e(TAG, "annexb not match."); SrsRtmp.srs_print_bytes(TAG, bb, 16); throw new Exception(String.format("annexb not match for %dB, pos=%d", bi.size, bb.position())); } // the start codes. ByteBuffer tbbs = bb.slice(); for (int i = 0; i < tbbsc.nb_start_code; i++) { bb.get(); } // find out the frame size. tbb.frame = bb.slice(); int pos = bb.position(); while (bb.position() < bi.size) { SrsAnnexbSearch bsc = utils.srs_avc_startswith_annexb(bb, bi); if (bsc.match) { break; } bb.get(); } tbb.size = bb.position() - pos; if (bb.position() < bi.size) { Log.i(TAG, String.format("annexb multiple match ok, pts=%d", bi.presentationTimeUs / 1000)); SrsRtmp.srs_print_bytes(TAG, tbbs, 16); SrsRtmp.srs_print_bytes(TAG, bb.slice(), 16); } //Log.i(TAG, String.format("annexb match %d bytes", tbb.size)); break; } return tbb; } } class SrsRawAacStreamCodec { public byte protection_absent; // SrsAacObjectType public int aac_object; public byte sampling_frequency_index; public byte channel_configuration; public short frame_length; public byte sound_format; public byte sound_rate; public byte sound_size; public byte sound_type; // 0 for sh; 1 for raw data. public byte aac_packet_type; public byte[] frame; } /** * remux the annexb to flv tags. */ class SrsFlv { private MediaFormat videoTrack; private MediaFormat audioTrack; private int achannel; private int asample_rate; private SrsUtils utils; private Handler handler; private SrsRawH264Stream avc; private byte[] h264_sps; private boolean h264_sps_changed; private byte[] h264_pps; private boolean h264_pps_changed; private boolean h264_sps_pps_sent; private byte[] aac_specific_config; public SrsFlv() { utils = new SrsUtils(); avc = new SrsRawH264Stream(); h264_sps = new byte[0]; h264_sps_changed = false; h264_pps = new byte[0]; h264_pps_changed = false; h264_sps_pps_sent = false; aac_specific_config = null; } /** * set the handler to send message to work thread. * @param h the handler to send the message. */ public void setHandler(Handler h) { handler = h; } public void setVideoTrack(MediaFormat format) { videoTrack = format; } public void setAudioTrack(MediaFormat format) { audioTrack = format; achannel = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); asample_rate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); } public void writeAudioSample(final ByteBuffer bb, MediaCodec.BufferInfo bi) throws Exception { int pts = (int)(bi.presentationTimeUs / 1000); int dts = (int)pts; byte[] frame = new byte[bi.size + 2]; byte aac_packet_type = 1; // 1 = AAC raw if (aac_specific_config == null) { frame = new byte[4]; // @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf // AudioSpecificConfig (), page 33 // 1.6.2.1 AudioSpecificConfig // audioObjectType; 5 bslbf byte ch = (byte)(bb.get(0) & 0xf8); // 3bits left. // samplingFrequencyIndex; 4 bslbf byte samplingFrequencyIndex = 0x04; if (asample_rate == SrsCodecAudioSampleRate.R22050) { samplingFrequencyIndex = 0x07; } else if (asample_rate == SrsCodecAudioSampleRate.R11025) { samplingFrequencyIndex = 0x0a; } ch |= (samplingFrequencyIndex >> 1) & 0x07; frame[2] = ch; ch = (byte)((samplingFrequencyIndex << 7) & 0x80); // 7bits left. // channelConfiguration; 4 bslbf byte channelConfiguration = 1; if (achannel == 2) { channelConfiguration = 2; } ch |= (channelConfiguration << 3) & 0x78; // 3bits left. // GASpecificConfig(), page 451 // 4.4.1 Decoder configuration (GASpecificConfig) // frameLengthFlag; 1 bslbf // dependsOnCoreCoder; 1 bslbf // extensionFlag; 1 bslbf frame[3] = ch; aac_specific_config = frame; aac_packet_type = 0; // 0 = AAC sequence header } else { bb.get(frame, 2, frame.length - 2); } byte sound_format = 10; // AAC byte sound_type = 0; // 0 = Mono sound if (achannel == 2) { sound_type = 1; // 1 = Stereo sound } byte sound_size = 1; // 1 = 16-bit samples byte sound_rate = 3; // 44100, 22050, 11025 if (asample_rate == 22050) { sound_rate = 2; } else if (asample_rate == 11025) { sound_rate = 1; } // for audio frame, there is 1 or 2 bytes header: // 1bytes, SoundFormat|SoundRate|SoundSize|SoundType // 1bytes, AACPacketType for SoundFormat == 10, 0 is sequence header. byte audio_header = (byte)(sound_type & 0x01); audio_header |= (sound_size << 1) & 0x02; audio_header |= (sound_rate << 2) & 0x0c; audio_header |= (sound_format << 4) & 0xf0; frame[0] = audio_header; frame[1] = aac_packet_type; SrsFlvFrameBytes tag = new SrsFlvFrameBytes(); tag.frame = ByteBuffer.wrap(frame); tag.size = frame.length; rtmp_write_packet(SrsCodecFlvTag.Audio, dts, 0, aac_packet_type, tag); } public void writeVideoSample(final ByteBuffer bb, MediaCodec.BufferInfo bi) throws Exception { int pts = (int)(bi.presentationTimeUs / 1000); int dts = (int)pts; ArrayList ibps = new ArrayList(); int frame_type = SrsCodecVideoAVCFrame.InterFrame; //Log.i(TAG, String.format("video %d/%d bytes, offset=%d, position=%d, pts=%d", bb.remaining(), bi.size, bi.offset, bb.position(), pts)); // send each frame. while (bb.position() < bi.size) { SrsFlvFrameBytes frame = avc.annexb_demux(bb, bi); // 5bits, 7.3.1 NAL unit syntax, // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame int nal_unit_type = (int)(frame.frame.get(0) & 0x1f); if (nal_unit_type == SrsAvcNaluType.SPS || nal_unit_type == SrsAvcNaluType.PPS) { Log.i(TAG, String.format("annexb demux %dB, pts=%d, frame=%dB, nalu=%d", bi.size, pts, frame.size, nal_unit_type)); } // for IDR frame, the frame is keyframe. if (nal_unit_type == SrsAvcNaluType.IDR) { frame_type = SrsCodecVideoAVCFrame.KeyFrame; } // ignore the nalu type aud(9) if (nal_unit_type == SrsAvcNaluType.AccessUnitDelimiter) { continue; } // for sps if (avc.is_sps(frame)) { byte[] sps = new byte[frame.size]; frame.frame.get(sps); if (utils.srs_bytes_equals(h264_sps, sps)) { continue; } h264_sps_changed = true; h264_sps = sps; continue; } // for pps if (avc.is_pps(frame)) { byte[] pps = new byte[frame.size]; frame.frame.get(pps); if (utils.srs_bytes_equals(h264_pps, pps)) { continue; } h264_pps_changed = true; h264_pps = pps; continue; } // ibp frame. SrsFlvFrameBytes nalu_header = avc.mux_ibp_frame(frame); ibps.add(nalu_header); ibps.add(frame); } write_h264_sps_pps(dts, pts); write_h264_ipb_frame(ibps, frame_type, dts, pts); } private void write_h264_sps_pps(int dts, int pts) { // when sps or pps changed, update the sequence header, // for the pps maybe not changed while sps changed. // so, we must check when each video ts message frame parsed. if (h264_sps_pps_sent && !h264_sps_changed && !h264_pps_changed) { return; } // when not got sps/pps, wait. if (h264_pps.length <= 0 || h264_sps.length <= 0) { return; } // h264 raw to h264 packet. ArrayList frames = new ArrayList(); avc.mux_sequence_header(h264_sps, h264_pps, dts, pts, frames); // h264 packet to flv packet. int frame_type = SrsCodecVideoAVCFrame.KeyFrame; int avc_packet_type = SrsCodecVideoAVCType.SequenceHeader; SrsFlvFrameBytes flv_tag = avc.mux_avc2flv(frames, frame_type, avc_packet_type, dts, pts); // the timestamp in rtmp message header is dts. rtmp_write_packet(SrsCodecFlvTag.Video, dts, frame_type, avc_packet_type, flv_tag); // reset sps and pps. h264_sps_changed = false; h264_pps_changed = false; h264_sps_pps_sent = true; Log.i(TAG, String.format("flv: h264 sps/pps sent, sps=%dB, pps=%dB", h264_sps.length, h264_pps.length)); } private void write_h264_ipb_frame(ArrayList ibps, int frame_type, int dts, int pts) { // when sps or pps not sent, ignore the packet. // @see https://github.com/simple-rtmp-server/srs/issues/203 if (!h264_sps_pps_sent) { return; } int avc_packet_type = SrsCodecVideoAVCType.NALU; SrsFlvFrameBytes flv_tag = avc.mux_avc2flv(ibps, frame_type, avc_packet_type, dts, pts); if (frame_type == SrsCodecVideoAVCFrame.KeyFrame) { //Log.i(TAG, String.format("flv: keyframe %dB, dts=%d", flv_tag.size, dts)); } // the timestamp in rtmp message header is dts. rtmp_write_packet(SrsCodecFlvTag.Video, dts, frame_type, avc_packet_type, flv_tag); } private void rtmp_write_packet(int type, int dts, int frame_type, int avc_aac_type, SrsFlvFrameBytes tag) { SrsFlvFrame frame = new SrsFlvFrame(); frame.tag = tag; frame.type = type; frame.dts = dts; frame.frame_type = frame_type; frame.avc_aac_type = avc_aac_type; // use handler to send the message. // TODO: FIXME: we must wait for the handler to ready, for the sps/pps cannot be dropped. if (handler == null) { Log.w(TAG, "flv: drop frame for handler not ready."); return; } Message msg = Message.obtain(); msg.what = SrsMessageType.FLV; msg.obj = frame; handler.sendMessage(msg); //Log.i(TAG, String.format("flv: enqueue frame type=%d, dts=%d, size=%dB", frame.type, frame.dts, frame.tag.size)); } } }