您当前的位置: 首页 >  音视频

命运之手

暂无认证

  • 1浏览

    0关注

    747博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【Android音视频开发】【032】Android从RTMP流中提取H264和AAC数据进行播放

命运之手 发布时间:2021-09-15 22:34:22 ,浏览量:1

前篇

在上篇博客中,我们已经讲解过,如何从RTMP流中提取H264和AAC数据,并保存为FLV,AAC,H264等文件

这篇博客我们讲解,怎么通过Android多媒体框架播放这些数据

上篇博客的重点在于通过C++进行解码,这篇重点则是通过Java进行播放

H264播放原理

创建SurfaceView,用于预览H264数据

SurfaceView初始化完毕,开始拉流,解析数据

解析完ScriptPacket后,得到视频参数,初始化MediaCodec

MediaCodec与SurfaceView绑定,将解码数据绘制到SurfaceView上

从RTMPPacket中解析出H264数据,通过JAVA回调,推送给MediaCodec解码

Activity销毁时,销毁MediaCodec

AAC播放原理

创建AudioTrack,用于播放PCM数据

解析完ScriptPacket后,得到音频参数,初始化AudioTrack和MediaCodec

从RTMPPacket中解析出AAC数据,通过JAVA回调,推送给MediaCodec解码

MediaCodec将AAC解码为PCM,交给AudioTrack播放

Activity销毁时,销毁MediaCodec和AudioTrack

核心代码


	//音频播放器

	package easing.android.media.RtmpPlayer.control;
	
	import android.media.AudioFormat;
	import android.media.AudioManager;
	import android.media.AudioTrack;
	import android.media.MediaCodec;
	import android.media.MediaCodecInfo;
	import android.media.MediaFormat;
	
	import java.nio.ByteBuffer;
	
	import lombok.SneakyThrows;
	
	@SuppressWarnings("all")
	public class AACPlayer {
	
	    AudioTrack audioTrack;
	    MediaCodec mediaCodec;
	
	    //代码是写死的,并未使用这些参数
	    //实际应用中,请根据字段值来初始化AudioTrack和MediaCodec
	    int sampleRate = 44100;
	    int sampleBit = 16;
	    int channelNum = 2;
	
	    boolean playing = false;
	
	    //设置音频参数
	    public void setAudioInfo(int sampleRate, int sampleBit, int channelNum) {
	        this.sampleRate = sampleRate;
	        this.sampleBit = sampleBit;
	        this.channelNum = channelNum;
	    }
	
	    @SneakyThrows
	    public void start() {
	        if (playing)
	            return;
	        //一次解码的数量
	        int inputBufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
	        //启动AudioTrack
	        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, inputBufferSize * 4, AudioTrack.MODE_STREAM);
	        audioTrack.play();
	        //启动MediaCodec
	        mediaCodec = MediaCodec.createDecoderByType("audio/mp4a-latm");
	        MediaFormat mediaFormat = new MediaFormat();
	        mediaFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
	        mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
	        mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
	        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 44100 * 16 * 2);
	        mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, inputBufferSize * 4);
	        mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
	        mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
	        //csd-0存储的是音频参数,csd-0占两个字节,格式为:
	        //AAC Profile占5位,Sample Rate占4位,Channel Num占4位,最后3位用0补位
	        //AAC Profile取值:AAC-Main 0x01,AAC-LC 0x02,AAC-SSR 0x03
	        //Sample Rate取值:44100Hz 0x04,48000Hz 0x03
	        //Channel Num取值:单声道 0x01,双声道 0x02
	        ByteBuffer csd_0 = ByteBuffer.wrap(new byte[]{0x12, 0x10});
	        mediaFormat.setByteBuffer("csd-0", csd_0);
	        mediaCodec.configure(mediaFormat, null, null, 0);
	        mediaCodec.start();
	        pts = 0L;
	        playing = true;
	    }
	
	    @SneakyThrows
	    public void stop() {
	        if (!playing)
	            return;
	        playing = false;
	        mediaCodec.stop();
	        mediaCodec.release();
	        mediaCodec = null;
	        audioTrack.stop();
	        audioTrack.release();
	        audioTrack = null;
	    }
	
	    //解码帧数据到Surface
	    public boolean decodeFrame(byte[] buffer, int offset, int length) {
	
	        if (!playing)
	            return false;
	
	        //input buffer data
	        ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
	        int inputBufferIndex = mediaCodec.dequeueInputBuffer(1000);
	        if (inputBufferIndex >= 0) {
	            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
	            inputBuffer.clear();
	            inputBuffer.put(buffer, offset, length);
	            mediaCodec.queueInputBuffer(inputBufferIndex, 0, length, getPTS(), 0);
	        }
	
	        //output decoded data
	        ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
	        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
	        int outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 1000);
	        while (outputBufferIndex >= 0) {
	            ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
	            byte[] decodeBuffer = new byte[info.size];
	            outputBuffer.get(decodeBuffer);
	            outputBuffer.clear();
	            audioTrack.write(decodeBuffer, offset, info.size);
	            mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
	            outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 1000);
	        }
	        return true;
	    }
	
	    long pts = 0L;
	
	    protected long getPTS() {
	        long dt = System.nanoTime() / 1000L - pts;
	        return dt;
	    }
	}




	//视频播放器

	package easing.android.media.RtmpPlayer.control;
	
	import android.content.Context;
	import android.media.MediaCodec;
	import android.media.MediaFormat;
	import android.util.AttributeSet;
	import android.view.SurfaceHolder;
	import android.view.SurfaceView;
	
	import java.nio.ByteBuffer;
	
	import easing.android.media.RtmpPlayer.util.ThreadUtils;
	import lombok.SneakyThrows;
	
	public class H264PlayView extends SurfaceView {
	
	    MediaCodec mediaCodec;
	
	    int width = 1280;
	    int height = 720;
	    int fps = 30;
	
	    boolean playing = false;
	
	    int frameIndex;
	
	    ThreadUtils.Action onSurfaceChange;
	
	    public H264PlayView(Context context) {
	        this(context, null);
	    }
	
	    public H264PlayView(Context context, AttributeSet attributeSet) {
	        super(context, attributeSet);
	        init(context, attributeSet);
	    }
	
	    //控件初始化
	    protected void init(Context context, AttributeSet attributeSet) {
	        getHolder().addCallback(new SurfaceHolder.Callback() {
	            @Override
	            @SneakyThrows
	            public void surfaceCreated(SurfaceHolder holder) {
	
	            }
	
	            @Override
	            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
	                if (onSurfaceChange != null)
	                    onSurfaceChange.runAndPostException();
	            }
	
	            @Override
	            public void surfaceDestroyed(SurfaceHolder holder) {
	                stop();
	            }
	        });
	    }
	
	    public void setVideoInfo(int width, int height, int fps) {
	        this.width = width;
	        this.height = height;
	        this.fps = fps;
	    }
	
	    @SneakyThrows
	    public void start() {
	        if (playing)
	            return;
	        mediaCodec = MediaCodec.createDecoderByType("video/avc");
	        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
	        mediaCodec.configure(mediaFormat, getHolder().getSurface(), null, 0);
	        mediaCodec.start();
	        playing = true;
	    }
	
	    @SneakyThrows
	    public void stop() {
	        if (!playing)
	            return;
	        playing = false;
	        mediaCodec.stop();
	        mediaCodec.release();
	        mediaCodec = null;
	    }
	
	    //解码帧数据到Surface
	    public boolean decodeFrame(byte[] buffer, int offset, int length) {
	
	        if (!playing)
	            return true;
	
	        // get input buffer index
	        ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
	        int inputBufferIndex = mediaCodec.dequeueInputBuffer(100);
	        if (inputBufferIndex = 0) {
	            mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
	            outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
	        }
	        return true;
	    }
	
	    //设置初始化回调
	    public H264PlayView onSurfaceChange(ThreadUtils.Action onSurfaceChange) {
	        this.onSurfaceChange = onSurfaceChange;
	        return this;
	    }
	
	    @Override
	    protected void onDetachedFromWindow() {
	        stop();
	        super.onDetachedFromWindow();
	    }
	}



	//控制界面

	package easing.android.media.RtmpPlayer;
	
	import android.os.Bundle;
	
	import androidx.appcompat.app.AppCompatActivity;
	
	import butterknife.BindView;
	import butterknife.ButterKnife;
	import easing.android.media.R;
	import easing.android.media.RtmpPlayer.control.AACPlayer;
	import easing.android.media.RtmpPlayer.control.H264PlayView;
	import easing.android.media.RtmpPlayer.util.CameraUtils;
	
	//这是一个简单的演示Demo
	//使用前请授予完整的存储卡访问权限
	@SuppressWarnings("all")
	public class PlayActivity extends AppCompatActivity {
	
	    @BindView(R.id.h264PlayView)
	    H264PlayView h264PlayView;
	
	    AACPlayer aacPlayer = new AACPlayer();
	
	    RtmpPlayer player = new RtmpPlayer();
	
	    final String url = "rtmp://mobliestream.c3tv.com:554/live/goodtv.sdp";
	
	    @Override
	    protected void onCreate(Bundle savedInstanceState) {
	        super.onCreate(savedInstanceState);
	        setContentView(R.layout.activity_play);
	        ButterKnife.bind(this, PlayActivity.this);
	
	        //设置拉流回调
	        player.listener = new RtmpPlayer.EventListener() {
	
	            @Override
	            public void onAudioParamChange(int sampleRate, int sampleBit, int channelNum) {
	                aacPlayer.stop();
	                aacPlayer.setAudioInfo(sampleRate, sampleBit, channelNum);
	                aacPlayer.start();
	            }
	
	            @Override
	            public void onVideoParamChange(int width, int height, int fps) {
	                h264PlayView.stop();
	                h264PlayView.setVideoInfo(width, height, fps);
	                h264PlayView.start();
	            }
	
	            @Override
	            public void onAudioData(byte[] buffer, int offset, int length) {
	                if (!isFinishing())
	                    aacPlayer.decodeFrame(buffer, offset, length);
	            }
	
	            @Override
	            public void onVideoData(byte[] buffer, int offset, int length) {
	                if (!isFinishing())
	                    h264PlayView.decodeFrame(buffer, offset, length);
	            }
	        };
	
	        //播放器初始化回调
	        h264PlayView.onSurfaceChange(() -> {
	            //开始拉流播放
	            player.initialize();
	            player.prepare(url);
	        });
	    }
	
	    @Override
	    protected void onDestroy() {
	        aacPlayer.stop();
	        player.release();
	        super.onDestroy();
	    }
	
	    @Override
	    protected void onResume() {
	        CameraUtils.setTranslucentMode(this);
	        super.onResume();
	    }
	}



源码下载

Android从RTMP流中提取H264和AAC数据进行播放.zip

关注
打赏
1654938663
查看更多评论
立即登录/注册

微信扫码登录

0.0419s