前篇
在上篇博客中,我们已经讲解过,如何从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