前篇

在上篇博客中,我们已经讲解过,如何从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和MediaCodecint 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;}@SneakyThrowspublic void start() {if (playing)return;//一次解码的数量int inputBufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);//启动AudioTrackaudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, inputBufferSize * 4, AudioTrack.MODE_STREAM);audioTrack.play();//启动MediaCodecmediaCodec = 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,双声道 0x02ByteBuffer 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;}@SneakyThrowspublic void stop() {if (!playing)return;playing = false;mediaCodec.stop();mediaCodec.release();mediaCodec = null;audioTrack.stop();audioTrack.release();audioTrack = null;}//解码帧数据到Surfacepublic boolean decodeFrame(byte[] buffer, int offset, int length) {if (!playing)return false;//input buffer dataByteBuffer[] 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 dataByteBuffer[] 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@SneakyThrowspublic void surfaceCreated(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {if (onSurfaceChange != null)onSurfaceChange.runAndPostException();}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {stop();}});}public void setVideoInfo(int width, int height, int fps) {this.width = width;this.height = height;this.fps = fps;}@SneakyThrowspublic 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;}@SneakyThrowspublic void stop() {if (!playing)return;playing = false;mediaCodec.stop();mediaCodec.release();mediaCodec = null;}//解码帧数据到Surfacepublic boolean decodeFrame(byte[] buffer, int offset, int length) {if (!playing)return true;// get input buffer indexByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();int inputBufferIndex = mediaCodec.dequeueInputBuffer(100);if (inputBufferIndex < 0) return false;ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];inputBuffer.clear();inputBuffer.put(buffer, offset, length);mediaCodec.queueInputBuffer(inputBufferIndex, 0, length, frameIndex * fps, 0);frameIndex++;// get output buffer indexMediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100);while (outputBufferIndex >= 0) {mediaCodec.releaseOutputBuffer(outputBufferIndex, true);outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);}return true;}//设置初始化回调public H264PlayView onSurfaceChange(ThreadUtils.Action onSurfaceChange) {this.onSurfaceChange = onSurfaceChange;return this;}@Overrideprotected 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";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_play);ButterKnife.bind(this, PlayActivity.this);//设置拉流回调player.listener = new RtmpPlayer.EventListener() {@Overridepublic void onAudioParamChange(int sampleRate, int sampleBit, int channelNum) {aacPlayer.stop();aacPlayer.setAudioInfo(sampleRate, sampleBit, channelNum);aacPlayer.start();}@Overridepublic void onVideoParamChange(int width, int height, int fps) {h264PlayView.stop();h264PlayView.setVideoInfo(width, height, fps);h264PlayView.start();}@Overridepublic void onAudioData(byte[] buffer, int offset, int length) {if (!isFinishing())aacPlayer.decodeFrame(buffer, offset, length);}@Overridepublic void onVideoData(byte[] buffer, int offset, int length) {if (!isFinishing())h264PlayView.decodeFrame(buffer, offset, length);}};//播放器初始化回调h264PlayView.onSurfaceChange(() -> {//开始拉流播放player.initialize();player.prepare(url);});}@Overrideprotected void onDestroy() {aacPlayer.stop();player.release();super.onDestroy();}@Overrideprotected void onResume() {CameraUtils.setTranslucentMode(this);super.onResume();}}

源码下载

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

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

  1. android flv 编码器,Android 音视频深入 十七 FFmpeg 获取 RTMP 流保存为 flv (附源码下载)...

    Android 音视频深入 十七 FFmpeg 获取 RTMP 流保存为 flv (附源码下载) 项目地址 https://github.com/979451341/RtmpSave 这个项目主要代码 ...

  2. Android 音视频开发(六) -- Android Mediaprojection 截屏和录屏

    Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍照功能 Andro ...

  3. Android 音视频深入 十七 FFmpeg 获取RTMP流保存为flv (附源码下载)

    项目地址 https://github.com/979451341/RtmpSave 这个项目主要代码我是从雷神那弄过来的,不愧是雷神,我就配个环境搞个界面就可以用代码了. 这一次说的是将RTMP流媒 ...

  4. Android 音视频开发学习思路

    Android 音视频开发这块目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的.只能通过一点点的学习和积累把这块的知识串联积累起来. 初级入门篇: Android 音视频开发(一) ...

  5. 那些年,Android音视频开发那些事儿

    音视频开发的主要应用有哪些? 音频播放器,录音机,语音电话,音视频监控应用,音视频直播应用,音频编辑/处理软件,蓝牙耳机/音箱,等等 1.视频监控类 (JNI+应用层开发) 从硬件到嵌入式再到软件,涉 ...

  6. android音频开发6,Android 音视频开发(一) : 通过三种方式绘制图片

    想要逐步入门音视频开发,就需要一步步的去学习整理,并积累.本文是音视频开发积累的第一篇. 对应的要学习的内容是:在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView ...

  7. Android 音视频开发(二):使用 AudioRecord 采集音频PCM并保存到文件(学习笔记)

    关于 AudioRecord Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风 ...

  8. 直播平台源码搭建教程之Android音视频开发

    直播平台源码搭建教程之Android音视频开发 音频 将声音保存成音频的过程,其实就是将模拟音频数字化的过程,为了实现这个过程,就需要对模拟音频进行采样.量化和编码.接下来我们详细讲解这一过程. 采样 ...

  9. Android 音视频开发之基础篇 使用 SurfaceView绘制一张图片

    Android 音视频开发 上一篇文章:使用 imageview绘制一张图片 任务一 SurfaceView绘制一张图片 文章目录 Android 音视频开发 前言 一.surfaceview是什么? ...

最新文章

  1. 阿里云智能对话分析服务
  2. VC++ GetSafeHwnd()和GetSafeHandle()
  3. 对抽象工厂+反射+配置文件的实例理解
  4. 01《软件需求分析教程》
  5. 给一个不多于5位的正整数,求出它是几位数?
  6. 一步步编写操作系统 38 一级页表与虚拟地址3
  7. django-正向查找
  8. windows上git clone命令速度过慢问题的解决
  9. JavaScript 开发进阶:理解 JavaScript 作用域和作用域链
  10. sublime3 多行编辑.摘抄
  11. 学前教育试题库及答案_学前教育学考试试题及答案
  12. 灵格斯怎么屏幕取词_完整页灵格斯词霸怎么用,灵格斯词霸使用教程_9号资讯
  13. HashMap解决hash冲突
  14. 计算两个坐标经纬度之间的距离(5种方式)
  15. Undefined symbols for architecture x86_64:xxxxxx, referenced from:
  16. 谷歌浏览器下面的任务栏不见了
  17. 在SQL中创建时间维度表
  18. 在maven官网下载maven历史版本
  19. Android获取设备内存数据信息
  20. 请教 ANDROID 通信信号、网络信号图标的颜色问题

热门文章

  1. 【linux常用命令】
  2. 基于Unity3D的Xml文件的存储的实现
  3. 使用 HBuilderX 打包IOS 和 安卓
  4. java的套娃汇总(黑马程序员基础java总结)
  5. 什么是数据规约,数据规约的策略都有哪些
  6. 深度神经网络及目标检测学习笔记
  7. mac os 多窗口固定顺序的方法
  8. 惊呆了!我用 Python 可视化分析和预测了 2022 年 FIFA世界杯
  9. 数据结构(王道版本,主讲人:闲鱼学长)P7-P18
  10. git lfs 使用