desc:h265 encode
							parent
							
								
									64a7cafe3f
								
							
						
					
					
						commit
						91054ba593
					
				| @ -1,114 +0,0 @@ | ||||
| /* | ||||
|  * Copyright 2014 Google Inc. All rights reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.yinuo.library.vlc.encoder; | ||||
| 
 | ||||
| import android.media.MediaCodec; | ||||
| import android.media.MediaCodecInfo; | ||||
| import android.media.MediaFormat; | ||||
| import android.util.Log; | ||||
| import android.view.Surface; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| /** | ||||
|  * This class wraps up the core components used for surface-input video encoding. | ||||
|  * <p> | ||||
|  * Once created, frames are fed to the input surface.  Remember to provide the presentation | ||||
|  * time stamp, and always call drainEncoder() before swapBuffers() to ensure that the | ||||
|  * producer side doesn't get backed up. | ||||
|  * <p> | ||||
|  * This class is not thread-safe, with one exception: it is valid to use the input surface | ||||
|  * on one thread, and drain the output on a different thread. | ||||
|  */ | ||||
| public class VideoEncoderCore extends MediaEncoderCore { | ||||
| 
 | ||||
|     // TODO: these ought to be configurable as well
 | ||||
|     private static final String MIME_TYPE = "video/avc";    // H.264 Advanced Video Coding
 | ||||
|     private static final int FRAME_RATE = 24;               // 30fps
 | ||||
|     private static final int IFRAME_INTERVAL = 1;           // 5 seconds between I-frames
 | ||||
|     private static final int BIT_RATE = 4000000; | ||||
| 
 | ||||
|     private Surface mInputSurface; | ||||
| 
 | ||||
|     /** | ||||
|      * Configures encoder and muxer state, and prepares the input Surface. | ||||
|      */ | ||||
|     public VideoEncoderCore(AndroidMuxer muxer, int width, int height) { | ||||
|         super(muxer); | ||||
| 
 | ||||
|         prepareEncoder(width, height); | ||||
| 
 | ||||
|         // Create a MediaMuxer.  We can't add the video track and start() the muxer here,
 | ||||
|         // because our MediaFormat doesn't have the Magic Goodies.  These can only be
 | ||||
|         // obtained from the encoder after it has started processing data.
 | ||||
|         //
 | ||||
|         // We're not actually interested in multiplexing audio.  We just want to convert
 | ||||
|         // the raw H.264 elementary stream we get from MediaCodec into a .mp4 file.
 | ||||
| //        mMuxer = new MediaMuxer(outputFile.toString(),
 | ||||
| //                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
 | ||||
|     } | ||||
| 
 | ||||
|     private void prepareEncoder(int width, int height) { | ||||
|         mBufferInfo = new MediaCodec.BufferInfo(); | ||||
| 
 | ||||
|         MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height); | ||||
| 
 | ||||
|         // Set some properties.  Failing to specify some of these can cause the MediaCodec
 | ||||
|         // configure() call to throw an unhelpful exception.
 | ||||
|         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, | ||||
|                 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); | ||||
|         format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); | ||||
|         format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); | ||||
|         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); | ||||
|         if (VERBOSE) Log.d(TAG, "format: " + format); | ||||
| 
 | ||||
|         // Create a MediaCodec encoder, and configure it with our format.  Get a Surface
 | ||||
|         // we can use for input and wrap it with a class that handles the EGL work.
 | ||||
| 
 | ||||
|         try { | ||||
|             mEncoder = MediaCodec.createEncoderByType(MIME_TYPE); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
| 
 | ||||
|         mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); | ||||
|         mInputSurface = mEncoder.createInputSurface(); | ||||
|         mEncoder.start(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the encoder's input surface. | ||||
|      */ | ||||
|     public Surface getInputSurface() { | ||||
|         return mInputSurface; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void start() { | ||||
|         drainEncoder(false); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void stop() { | ||||
|         drainEncoder(true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected boolean isSurfaceInput() { | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,205 @@ | ||||
| /* | ||||
|  * Copyright 2014 Google Inc. All rights reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.yinuo.library.vlc.encoder; | ||||
| 
 | ||||
| import android.media.MediaCodec; | ||||
| import android.media.MediaCodecInfo; | ||||
| import android.media.MediaFormat; | ||||
| import android.util.Log; | ||||
| import android.view.Surface; | ||||
| 
 | ||||
| import com.yinuo.library.vlc.PushHelper; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.ByteBuffer; | ||||
| 
 | ||||
| /** | ||||
|  * This class wraps up the core components used for surface-input video encoding. | ||||
|  * <p> | ||||
|  * Once created, frames are fed to the input surface.  Remember to provide the presentation | ||||
|  * time stamp, and always call drainEncoder() before swapBuffers() to ensure that the | ||||
|  * producer side doesn't get backed up. | ||||
|  * <p> | ||||
|  * This class is not thread-safe, with one exception: it is valid to use the input surface | ||||
|  * on one thread, and drain the output on a different thread. | ||||
|  */ | ||||
| public class VideoEncoderCoreHevc extends MediaEncoderCore { | ||||
| 
 | ||||
|     // TODO: these ought to be configurable as well
 | ||||
|     private static final String MIME_TYPE = "video/hevc";    // H.264 Advanced Video Coding
 | ||||
|     private static final int FRAME_RATE = 24;               // 30fps
 | ||||
|     private static final int IFRAME_INTERVAL = 1;           // 5 seconds between I-frames
 | ||||
|     private static final int BIT_RATE = 4000000; | ||||
| 
 | ||||
|     private Surface mInputSurface; | ||||
| 
 | ||||
|     /** | ||||
|      * Configures encoder and muxer state, and prepares the input Surface. | ||||
|      */ | ||||
|     public VideoEncoderCoreHevc(AndroidMuxer muxer, int width, int height) { | ||||
|         super(muxer); | ||||
|         prepareEncoder(width, height); | ||||
|         PushHelper.INSTANCE.startStream(true); | ||||
|     } | ||||
| 
 | ||||
|     private void prepareEncoder(int width, int height) { | ||||
|         mBufferInfo = new MediaCodec.BufferInfo(); | ||||
| 
 | ||||
|         MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height); | ||||
| 
 | ||||
|         // Set some properties.  Failing to specify some of these can cause the MediaCodec
 | ||||
|         // configure() call to throw an unhelpful exception.
 | ||||
|         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, | ||||
|                 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); | ||||
|         format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); | ||||
|         format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); | ||||
|         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); | ||||
|         if (VERBOSE) Log.d(TAG, "format: " + format); | ||||
| 
 | ||||
|         // Create a MediaCodec encoder, and configure it with our format.  Get a Surface
 | ||||
|         // we can use for input and wrap it with a class that handles the EGL work.
 | ||||
|         try { | ||||
|             mEncoder = MediaCodec.createEncoderByType(MIME_TYPE); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
| 
 | ||||
|         mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); | ||||
|         mInputSurface = mEncoder.createInputSurface(); | ||||
|         mEncoder.start(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the encoder's input surface. | ||||
|      */ | ||||
|     @Override | ||||
|     public Surface getInputSurface() { | ||||
|         return mInputSurface; | ||||
|     } | ||||
| 
 | ||||
|     public static final int NAL_I = 19; | ||||
|     public static final int NAL_VPS = 32; | ||||
|     private byte[] vps_sps_pps_buf; | ||||
| 
 | ||||
|     @Override | ||||
|     public void drainEncoder(boolean endOfStream) { | ||||
|         buildKeyFrame(); | ||||
|         final int TIMEOUT_USEC = 10000; | ||||
|         if (VERBOSE) Log.d(TAG, "drainEncoder(" + endOfStream + ")"); | ||||
| 
 | ||||
|         if (endOfStream && isSurfaceInput()) { | ||||
|             if (VERBOSE) Log.d(TAG, "sending EOS to encoder"); | ||||
|             mEncoder.signalEndOfInputStream(); | ||||
|         } | ||||
|         byte[] mSpsPps = new byte[0]; | ||||
|         ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers(); | ||||
|         int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); | ||||
| 
 | ||||
|         if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { | ||||
|             // no output available yet
 | ||||
|             if (!endOfStream) { | ||||
|             } else { | ||||
|                 if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS"); | ||||
|             } | ||||
|         } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { | ||||
|             // not expected for an encoder
 | ||||
|             encoderOutputBuffers = mEncoder.getOutputBuffers(); | ||||
|         } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { | ||||
|             // should happen before receiving buffers, and should only happen once
 | ||||
|             MediaFormat newFormat = mEncoder.getOutputFormat(); | ||||
|             Log.d(TAG, "encoder output format changed: " + newFormat); | ||||
| 
 | ||||
|             // now that we have the Magic Goodies, start the muxer
 | ||||
|             mTrackIndex = mMuxer.addTrack(newFormat); | ||||
|         } else if (encoderStatus < 0) { | ||||
|             Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " + | ||||
|                     encoderStatus); | ||||
|             // let's ignore it
 | ||||
|         } else { | ||||
|             ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; | ||||
|             if (encodedData == null) { | ||||
|                 throw new RuntimeException("encoderOutputBuffer " + encoderStatus + | ||||
|                         " was null"); | ||||
|             } | ||||
| 
 | ||||
|             if (!mMuxer.isStarted()) { | ||||
|                 mBufferInfo.size = 0; | ||||
|             } | ||||
| 
 | ||||
|             if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { | ||||
|                 // The codec config data was pulled out and fed to the muxer when we got
 | ||||
|                 // the INFO_OUTPUT_FORMAT_CHANGED status.  Ignore it.
 | ||||
|                 if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG"); | ||||
|                 mBufferInfo.size = 0; | ||||
|             } | ||||
| 
 | ||||
|             if (mBufferInfo.size != 0) { | ||||
|                 // adjust the ByteBuffer values to match BufferInfo (not needed?)
 | ||||
|                 encodedData.position(mBufferInfo.offset); | ||||
|                 encodedData.limit(mBufferInfo.offset + mBufferInfo.size); | ||||
| 
 | ||||
|                 mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); | ||||
|                 if (VERBOSE) { | ||||
|                     Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + | ||||
|                             mBufferInfo.presentationTimeUs); | ||||
|                 } | ||||
| 
 | ||||
|                 byte[] outData = new byte[mBufferInfo.size]; | ||||
|                 encodedData.get(outData); | ||||
|                 int offset = 4; | ||||
|                 if (outData[2] == 0x01) { | ||||
|                     offset = 3; | ||||
|                 } | ||||
|                 int type = (outData[offset] & 0x7E) >> 1; | ||||
|                 if (type == NAL_VPS) { | ||||
|                     vps_sps_pps_buf = outData; | ||||
|                 } else if (type == NAL_I) { | ||||
|                     byte[] newBuf = new byte[vps_sps_pps_buf.length + outData.length]; | ||||
|                     System.arraycopy(vps_sps_pps_buf, 0, newBuf, 0, vps_sps_pps_buf.length); | ||||
|                     System.arraycopy(outData, 0, newBuf, vps_sps_pps_buf.length, outData.length); | ||||
|                     outData = newBuf; | ||||
|                 } | ||||
|                 PushHelper.INSTANCE.pushData(outData, outData.length, mBufferInfo.presentationTimeUs / 1000); | ||||
|             } | ||||
| 
 | ||||
|             mEncoder.releaseOutputBuffer(encoderStatus, false); | ||||
| 
 | ||||
|             if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { | ||||
|                 if (!endOfStream) { | ||||
|                     Log.w(TAG, "reached end of stream unexpectedly"); | ||||
|                 } else { | ||||
|                     if (VERBOSE) Log.d(TAG, "end of stream reached"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void start() { | ||||
|         drainEncoder(false); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void stop() { | ||||
|         drainEncoder(true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected boolean isSurfaceInput() { | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
					Loading…
					
					
				
		Reference in New Issue