override public async void Run() { //Android.Media.MediaExtractor extractor; Android.Media.MediaCodec decoder = null; using (var extractor = new Android.Media.MediaExtractor()) //using (Android.Media.MediaCodec decoder = null) { //extractor = new Android.Media.MediaExtractor(); try { await extractor.SetDataSourceAsync(SAMPLE).ConfigureAwait(false); } catch (Exception ex) { var s = ex.ToString(); return; } for (int i = 0; i < extractor.TrackCount; i++) { var format = extractor.GetTrackFormat(i); Log.Debug("Format info: ", format.ToString()); String mime = format.GetString(Android.Media.MediaFormat.KeyMime); if (mime.StartsWith("video/")) { Log.Debug("Format mime: ", mime); //Log.Debug("Format " + MediaFormat.KeyMaxInputSize + ": ", // format.GetInteger(MediaFormat.KeyMaxInputSize).ToString()); Log.Debug("Format " + MediaFormat.KeyWidth + ": ", format.GetInteger(MediaFormat.KeyWidth).ToString()); Log.Debug("Format " + MediaFormat.KeyHeight + ": ", format.GetInteger(MediaFormat.KeyHeight).ToString()); PrintFormatInfo(format); extractor.SelectTrack(i); //decoder = Android.Media.MediaCodec.CreateDecoderByType(mime); //this is where the Xamarin Android VM dies. //decoder.Configure(format, surface, null, 0); break; } } //if (decoder == null) //{ // Android.Util.Log.Error("DecodeActivity", "Can't find video info!"); // return;//can't continue... //} var f = new Java.IO.File(dir+"decode.out"); if (f.Exists()) f.Delete(); f.CreateNewFile(); var f2 = new Java.IO.File(dir + "decode2.out"); if (f2.Exists()) f2.Delete(); f2.CreateNewFile(); //open the file for our custom extractor var inInfo = new System.IO.FileInfo(SAMPLE); if (!inInfo.Exists) { Log.Error("input file not found!", inInfo.FullName); return; } using (var inStream = inInfo.OpenRead()) using (var fs2 = new Java.IO.FileOutputStream(f2))//get an output stream using (var fs = new Java.IO.FileOutputStream(f))//get an output stream { //var inputBuffers = decoder.GetInputBuffers(); //var outputBuffers = decoder.GetOutputBuffers(); var info = new Android.Media.MediaCodec.BufferInfo(); bool started = false, isEOS = false; var sw = new System.Diagnostics.Stopwatch(); long startMs = sw.ElapsedMilliseconds; sw.Start(); byte[] peekBuf = new byte[188]; //for dumping the sample into instead of the decoder. var buffer = Java.Nio.ByteBuffer.Allocate(165000);// decoder.GetInputBuffer(inIndex); var buffEx = new BufferExtractor(); var tmpB = new byte[20000]; while (!interrupted) { //sw.Restart(); if (!isEOS) { int inIndex = 1;// decoder.DequeueInputBuffer(10000); if (inIndex >= 0) { buffer.Position(0);//reset the buffer if (buffer.Position() != 0) Log.Debug("inBuff.Position: ", buffer.Position().ToString()); Log.Debug("inBuff: ", buffer.ToString()); int sampleSize = extractor.ReadSampleData(buffer, 0); if (sampleSize < 0) { // We shouldn't stop the playback at this point, just pass the EOS // flag to decoder, we will get it again from the // dequeueOutputBuffer Log.Debug("DecodeActivity", MediaCodecBufferFlags.EndOfStream.ToString()); //decoder.QueueInputBuffer(inIndex, 0, 0, 0, MediaCodecBufferFlags.EndOfStream); isEOS = true; } else { if (peekBuf.Length < sampleSize) peekBuf = new byte[sampleSize]; peekBuf.Initialize();//clear old data. buffer.Get(peekBuf); buffer.Position(0);//reset for the decoder for (int i = 4; i < peekBuf.Length; ++i) { if (peekBuf[i] == 0x01 && peekBuf[i - 1] == 0x00 && peekBuf[i - 2] == 0x00 && peekBuf[i - 3] == 0x00) Log.Debug("Found h264 start code: ", string.Format("i={0} of {1}", i, sampleSize)); } Log.Debug("ExtractorActivity, sampleSize: ", sampleSize.ToString()); if (!started)//get your parser synced with theirs { do { peekBuf = new byte[188]; await inStream.ReadAsync(peekBuf, 0, peekBuf.Length) .ConfigureAwait(false); buffEx.AddRaw(peekBuf); if (buffEx.outBuffers.Count > 0 && buffEx.outBuffers.Peek().GetPayload().Length != sampleSize) { buffEx.outBuffers.Dequeue();//throw this one away } } while (buffEx.outBuffers.Count == 0); started = true; } else { do { peekBuf = new byte[188]; await inStream.ReadAsync(peekBuf, 0, peekBuf.Length) .ConfigureAwait(false); buffEx.AddRaw(peekBuf); } while (buffEx.outBuffers.Count == 0); started = true; } //write out the vid data. buffer.Limit(sampleSize); buffer.Position(0); //if (tmpB.Length < sampleSize) tmpB = new byte[sampleSize]; buffer.Get(tmpB); fs.Write(tmpB); buffer.Limit(buffer.Capacity());//reset the limit for next sample buffer.Position(0); fs2.Write(buffEx.outBuffers.Dequeue().GetPayload()); if (!inStream.CanRead) isEOS = true;//end of stream. //decoder.QueueInputBuffer(inIndex, 0, sampleSize, extractor.SampleTime, 0); await extractor.AdvanceAsync().ConfigureAwait(false); //extractor.AdvanceAsync(); } } } // All decoded frames have been rendered, we can stop playing now if ((info.Flags & MediaCodecBufferFlags.EndOfStream) != 0) { Android.Util.Log.Debug("DecodeActivity", MediaCodecBufferFlags.EndOfStream.ToString()); break; } } //decoder.Stop(); } } }
protected override async void Run() { try { running = true; var finfo = new System.IO.FileInfo(FilePlayer.SAMPLE);//FilePlayer.dir + "decode.out"); var fs = finfo.OpenRead(); int bytes, count, inIndex =0; int buffSize = TsPacket.PacketLength*4;//simulate multiple TS packets grouped into a single UDP packet var buff = new byte[buffSize]; var ts = new MpegTS.TsPacket(buff); buffEx = new BufferExtractor(); //buffEx.SampleReady += BuffEx_SampleReady; bool eof = false; using(info) using(decoder) using (fs) { count = 0; Log.Debug(TAG, "looking for format info"); ////look for the format info. //do //{ // bytes = await fs.ReadAsync(buff, 0, buff.Length).ConfigureAwait(false); // Log.Debug(TAG, "PID: " + string.Format("{0}", ts.PID)); //} while (BitConverter.ToInt32(buff, formatStartI) != formatStartVal); //Log.Debug(TAG, "found format info"); //var tmpB = new byte[23 + 8]; //System.Buffer.BlockCopy(buff, formatStartI - 4, tmpB, 0, tmpB.Length); InitializeDecoder(); fs.Position = 0;//reset sw.Restart(); //bool started = false; do { ++count; try { while (fs.CanRead && buffEx.SampleCount == 0) { if (fs.Length - fs.Position < buffSize) { eof = true; break;//we're @ EOF } //we need a new buffer every loop! buff = new byte[buffSize]; bytes = await fs.ReadAsync(buff, 0, buff.Length) .ConfigureAwait(false); //push the raw data to our custom extractor if (!buffEx.AddRaw(buff)) { Log.Debug("ExtractorActivity, ", " ----------bad TS packet!"); //find next sync byte and try again fs.Position -= buff.Length - buff.ToList().IndexOf(MpegTS.TsPacket.SyncByte); } } if (!fs.CanRead || eof) break; } catch (Exception ex) { Log.Error("ExtractorActivity error: ", ex.ToString()); } //get the raw video stream, stripped of Mpeg TS headers var sample = buffEx.DequeueNextSample(); Log.Debug("ExtractorActivity, sampleSize: ", sample.Length.ToString()); //var outputBuffers = decoder.GetOutputBuffers(); //get a input buffer index from the decoder for input inIndex = decoder.DequeueInputBuffer(10000); if (inIndex >= 0) { //get the re-assembled video data from the extractor using (var b = Java.Nio.ByteBuffer.Wrap(sample.Buffer)) { var inB = inputBuffers[inIndex]; //************* //THE BUFFER *******MUST********* be CLEARED before each write, //else when the buffers start getting recycled, the decoder will //read past the end of the current data into old data! //This may cause tearing of the picture, or even a complete //crash of the app from internal errors in native decoder code!!!!! inB.Clear(); inB.Put(b);//put data into the decoder's native buffer //tell the decoder about the new data in the buffer decoder.QueueInputBuffer(inIndex, 0, b.Limit(), 0, MediaCodecBufferFlags.None); }// b.Dispose();//clean up } //else // continue;//we don't have a full video frame, look for more. //check decoder output/state int outIndex = decoder.DequeueOutputBuffer(info, 10000); switch ((Android.Media.MediaCodecInfoState)outIndex) { case MediaCodecInfoState.OutputBuffersChanged: Android.Util.Log.Debug("DecodeActivity", MediaCodecInfoState.OutputBuffersChanged.ToString()); outputBuffers = decoder.GetOutputBuffers(); break; case MediaCodecInfoState.OutputFormatChanged: Android.Util.Log.Debug("DecodeActivity", "New format " + decoder.OutputFormat);//.GetOutputFormat(outIndex)); break; case MediaCodecInfoState.TryAgainLater: Android.Util.Log.Debug("DecodeActivity", "dequeueOutputBuffer timed out!"); break; default: var buffer = outputBuffers[outIndex];// decoder.GetOutputBuffer(outIndex); Android.Util.Log.Verbose("DecodeActivity", "render the buffer, " + buffer); //bool gcDone = false; // We use a very simple clock to keep the video FPS, or the video // playback will be too fast //This causes the next frame to not be rendered too quickly. while (info.PresentationTimeUs / 1000 > sw.ElapsedMilliseconds) { await Task.Delay(10).ConfigureAwait(false); } //the decoder won't advance without this... //must be called before the next decoder.dequeue call decoder.ReleaseOutputBuffer(outIndex, true); break; } } while (fs.CanRead && running); Log.Debug("DecodeActivity", MediaCodecBufferFlags.EndOfStream.ToString()); try { decoder.QueueInputBuffer(inIndex, 0, 0, 0, MediaCodecBufferFlags.EndOfStream); } catch (Exception ex) { Log.Debug("DecodeActivity", "error closing decoder!"); } }//dispose filestream,decoder, info } catch (Exception ex) { Log.Debug(this.GetType().Name, ex.ToString()); } info = null; decoder = null; }