前言
1,软编(FFmpeg)可以播放任何类型的视频,而硬编(Mediacodec)播放的视频有限。
2,IJKPlayer,哔哩哔哩基于FFmpeg展开,是对FFmpeg的封装。
3,FFmpeg,有很多平台,并不是单单为了Android平台。
4,FFmpeg,完全可以替代x264。
5,FFmpeg,是用C开发的,注意不是c++,如果要引用c++,需要加extern “C”
6,引入第三方库需要引入.so文件和头文件,需要手动配置cmake
7,在FFmpeg中,凡是大于等于0就是正常,反之,就是不正常。
视频架构
一,第一步,拷贝库文件;第二步,拷贝头文件;第三步,配置cmake,buildgradle。
1,导入头文件
注意,一定要有平台文件夹,这里是armeabi-v7a
2,gradle配置,指定平台是armeabi-v7a
指定so库路径
3,cmake配置
引入头文件,s表示include中所有的文件
引入库文件
二:万能播放器的架构
1, MP4是个容器,包括视频轨道、音频轨道、字幕轨道等
流0,流1就是轨道,哪个是音频轨道哪个是视频轨道不一定。所以我们首先要确定哪个是音频轨道,哪个是视频轨道。
2,读取视频,需要开辟一个线程,叫读取线程,读取一个数据包(压缩数据)后,这时候虽然可以渲染到屏幕,但拿出来就渲染很不稳定。
3,解码数据包的速度与IO的速度不一致,通常IO的速度较快。这个时候我们需要使用生产者模式和消费者模式。生产者就是读取线程,消费者就是把数据渲染到屏幕,生产者和消费者之间会有一个队列(这个队列要用c做)。队列里面存放的就是压缩好的数据包(AVPacket)
4,生产者与消费者不能再同一个线程,一个叫读取线程,一个叫解码线程。
5,我们首先需要判断这个数据包是视频数据包还是音频数据包。
6,流程如下。
7,
,
视频解码器 AVCodec ,视频上下文 AVCodecContext,
8,AvPacet里是压缩数据,其本质是码流,其内的data是一个字节数组,data里面存放的就是
要通过解码器解码成AVFrame
9,队列里面不能存放过多ACPacket,存放多了内存不够用。
10,现在我们得到了YUV(videoFrame->data),但还是不能直接渲染。原因一:surface不支持yuv,只支持RGB,因二:视频宽高和控件宽高不一致,1:1的方式渲染只会渲染一部分。我们不得不用大部分代码去解决这个问题。
11,拿到AvFrame,其里面有个data,装的就是YUV数据,但Surface只支持RGB,而不支持YUV。surface是Java对象,native 方法中,也根本没有提供surface的API(相关方法)。
解决方法:1,定位到Surface属于哪个native window(每个surface都属于一个window),该window脱离Android,不归Android UI 绘制管理。2,设置属性,surface要加载哪些属性。比如宽高格式之类的。3,根据native window 查找一个缓冲区,这个缓冲区叫做ANativeWindow_Buffer,里面有个指针叫bits,指向了surfaceView显示的缓冲区。4,把视频的YUV转换成RGB,赋值给bits值,surfaceView就会显示。
videoFrame是给FFmpeng初始化,初始化的时候会设值,比如宽高显示格式,容器大小等。我们需要确定RGB的AvFrame的容器的大小。
视频的YUV转换为视频的RGB需要一个转换器,这个转换器需要上下文。
12,视频解码后的大小与格式无关(h264还是h265),跟I帧,p帧,B帧也没关系。只跟宽高和编码格式有关。
音频架构
1,音频喇叭输出,有audiotrack、openes sl等方式
2,音频解码:AVPacket要转换为AVFrame(同视频),AVFrame要转换,需要重新采样。也需要转换器
3,播放器会实例化AudioTrack。
4,解码B帧最慢,因为B帧最小,还原最多,还原后各帧大小一样。
5,音视频不同步的原理:
解码视频的时候各帧(解码B帧最慢)的速度不一样。音频是一种波,算法是一样的,不存在忽快忽慢的问题,匀速解码。
6,如何做同步:
以音频为准,
7,音频倍速:
重新整理出音频波形,达到倍数的效果,需要使用soundtouch库
代码
#include <jni.h>
#include <string>
#include <android/log.h>
#include <pthread.h>
#include <unistd.h>
#include <android/native_window_jni.h>
extern "C" {
#include <libavformat/avformat.h>
#include "libavcodec/avcodec.h"
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>
}
#include "MNQueue.h"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"david",__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_INFO,"h264层",__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_INFO,"解码层",__VA_ARGS__)
#define LOGV(...) __android_log_print(ANDROID_LOG_INFO,"同步层",__VA_ARGS__)
#define LOGQ(...) __android_log_print(ANDROID_LOG_INFO,"队列层",__VA_ARGS__)
#define LOGA(...) __android_log_print(ANDROID_LOG_INFO,"音频",__VA_ARGS__)
//视频索引
int videoIndex = -1;
//音频索引
int audioIndex = -1;
//视频队列
MNQueue *videoQueue;
//音频队列
MNQueue *audioQueue;
//视频队列
uint8_t *outbuffer;
//ffmpeg c 代码
ANativeWindow *nativeWindow;
AVCodecContext *videoContext;
AVCodecContext *audioContext;
AVFormatContext *avFormatContext;
ANativeWindow_Buffer windowBuffer;
AVFrame *rgbFrame;
AVFrame *audioFrame;
int width;
int height;
bool isStart = false;
SwsContext *swsContext;
jmethodID playTrack;
jobject mInstance;
_JavaVM *javaVM = NULL;
//获取到主线程的jvm实例,凡是在子线程调用Java主线程,必须重新此方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
jint result = -1;
javaVM = vm;
JNIEnv *env;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
return JNI_VERSION_1_4;
}
//SwrContext *swrContext;
//void *decodeAudio(void *pVoid) {
// LOGI("开启解码音频线程");
申请avframe,装解码后的数据
// AVFrame *frame = av_frame_alloc();
// int out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
转换器上下文
// swrContext = swr_alloc();
// uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
// enum AVSampleFormat out_formart = AV_SAMPLE_FMT_S16;
// int out_sample_rate = audioContext->sample_rate;
转换器的代码
// swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
输出的
// audioContext->channel_layout, audioContext->sample_fmt,
// audioContext->sample_rate, 0, NULL
// );
初始化转化上下文
// swr_init(swrContext);
1s的pcm个数
// uint8_t *outbuffer = (uint8_t *) av_malloc(44100 * 2);
// LOGD("开启解码音频线程");
// while (isStart) {
//
// AVPacket *audioPacket = av_packet_alloc();
// LOGQ("decodeAudio 队列 前 %d ", audioQueue->size());
// audioQueue->get(audioPacket);
// LOGQ("decodeAudio 队列 后 %d ", audioQueue->size());
音频的数据
// int ret = avcodec_send_packet(audioContext, audioPacket);
// LOGD("send_packet 音频数据%d", ret);
// if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
// LOGD("解码出错 %d", ret);
// continue;
// }
// ret = avcodec_receive_frame(audioContext, frame);
// LOGD("receive_frame 音频数据%d", ret);
// if (ret != 0) {
// av_packet_free(&audioPacket);
// av_free(audioPacket);
// audioPacket = NULL;
// continue;
// }
// if (ret >= 0) {
//
输出的我们写完了 我们再写输入数据
// swr_convert(swrContext, &outbuffer, 44100 * 2,
// (const uint8_t **) (frame->data), frame->nb_samples);
//
解码了
// int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,
// AV_SAMPLE_FMT_S16, 1);
java的字节数组
// JNIEnv *jniEnv;
// LOGD("获取pcm数据 %d ", size);
// if (javaVM->AttachCurrentThread(&jniEnv, 0) != JNI_OK) {
// continue;
// }
// jbyteArray byteArrays = jniEnv->NewByteArray(size);
// LOGD("AttachCurrentThread %d ", size);
// jniEnv->SetByteArrayRegion(byteArrays, 0, size,
// reinterpret_cast<const jbyte *>(outbuffer));
// LOGD("--------1----------------mInstance %p playTrack %p byteArrays %p",
// mInstance, playTrack, byteArrays);
// jniEnv->CallVoidMethod(mInstance, playTrack, byteArrays, size);
// LOGD("--------2----------------");
// jniEnv->DeleteLocalRef(byteArrays);
// LOGD("--------3----------------");
// javaVM->DetachCurrentThread();
// }
// }
//}
//
void *decodeAudio(void *pVoid) {
LOGI("==========解码音频线程");
//申请avframe,装解码后的数据
AVFrame *frame = av_frame_alloc();
//初始化一个音频转换上下文
SwrContext *swrContext = swr_alloc();
// 双通道
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
// 采样位数
enum AVSampleFormat out_formart = AV_SAMPLE_FMT_S16;
// 采样频率
int out_sample_rate = audioContext->sample_rate;
// 设值转换器上下文
/**
* out_ch_layout 通道个数
* out_formart 采样位数
* out_sample_rate 采样频率
*/
swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
audioContext->channel_layout, audioContext->sample_fmt,
audioContext->sample_rate,
0, NULL);
// 初始换音频
swr_init(swrContext);
// 1s的pcm个数
uint8_t *outbuffer = (uint8_t *) av_malloc(44100 * 2);
// 双通道的值
int out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
while (isStart) {
AVPacket *audioPacket = av_packet_alloc();
audioQueue->get(audioPacket);
int ret = avcodec_send_packet(audioContext, audioPacket);
if (ret != 0) {
av_packet_free(&audioPacket);
av_free(audioPacket);
audioPacket = NULL;
continue;
}
// 音频不存在视频先发后收的问题。
ret = avcodec_receive_frame(audioContext, frame);
LOGD("receive_frame 音频数据%d", ret);
if (ret != 0) {
av_packet_free(&audioPacket);
av_free(audioPacket);
audioPacket = NULL;
continue;
}
if (ret >= 0) {//大于等于0才处理。现在数据在AvFrame的data中,
/**
* outbuffer:目的地
* (const uint8_t **) (frame->data):数据源
* 44100 * 2:outbuffer的长度,一个点的范围是65535(-32767到32768),正好是两个字节
* frame->nb_samples:解码后的音频帧有多少个
*/
swr_convert(swrContext, &outbuffer, 44100 * 2,
(const uint8_t **) (frame->data), frame->nb_samples);
// 获取转换之后的个数
int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,
AV_SAMPLE_FMT_S16, 1);
// 回调Java层,子线程回调和主线程回调不一样,我们现在是在子线程
JNIEnv *jniEnv;
// 通过jvm拿到env。
if (javaVM->AttachCurrentThread(&jniEnv, 0) != JNI_OK) {
continue;
}
//把c的byge数组 转换为Java的byte数组需要一个Java数组的容器,我们这里实例化这个容器
jbyteArray byteArrays = jniEnv->NewByteArray(size);
// 把outbuffer数据塞到byteArrays中。
jniEnv->SetByteArrayRegion(byteArrays, 0, size,
reinterpret_cast<const jbyte *>(outbuffer));
jniEnv->CallVoidMethod(mInstance, playTrack, byteArrays, size);
LOGD("--------2----------------");
// 释放全局变量
jniEnv->DeleteLocalRef(byteArrays);
LOGD("--------3----------------");
javaVM->DetachCurrentThread();
}
}
av_frame_free(&frame);
swr_free(&swrContext);
avcodec_close(audioContext);
avformat_close_input(&avFormatContext);
return NULL;
}
void *decodeVideo(void *pVoid) {
LOGI("==========解码视频线程");
while (isStart) {
// 需要一个饭缸(容器)
AVPacket *videoPacket = av_packet_alloc();
// 取数据:有数据就会取出来,没有数据就会阻塞。不需要休眠,因为你如果小于0就会阻塞这里。
videoQueue->get(videoPacket);
// 以前解码使用的是dsp芯片,但现在我们用的是cpu解码,
// ffmpeg是个函数库,我们把I帧丢进去,I帧会解码成YUV数据,然后渲染。
// I帧放进去后,我们把P帧丢进去,此时不应该输出p帧,FFmpeg得到p帧数据后就会
// 解析,然后进行缓存,接下来是B帧,B帧会被解码成YUV,输出。最后输出P帧
// FFmpeg中有两个方法,一个是输入(avcodec_send_packet,把帧送进去,但未必输出),一个是输出avcodec_receive_frame
int ret = avcodec_send_packet(videoContext, videoPacket);
if (ret != 0) {
// 释放videoPacket
av_packet_free(&videoPacket);
av_free(videoPacket);
videoPacket = NULL;
continue;
}
// 容器 饭缸 要拿到YUV数据,必须要有一个容器。
AVFrame *videoFrame = av_frame_alloc();
//videoContext视频上下文,videoFrame就是解码后的数据,
// videoFrame是给FFmpeng初始化,初始化的时候会设值,比如宽高显示格式,容器大小等。
avcodec_receive_frame(videoContext, videoFrame);
if (ret != 0) {
av_frame_free(&videoFrame);
av_free(videoFrame);
videoFrame = NULL;
av_packet_free(&videoPacket);
av_free(videoPacket);
videoPacket = NULL;
LOGE("=================");
continue;
}
//现在我们得到了YUV(videoFrame->data),但还是不能直接渲染。原因一:surface不支持yuv,只支持RGB,因二:视频宽高和控件宽高不一致,1:1的方式渲染只会渲染一部分
//开始转换:视频的YUV转换为视频的RGB,yuv的AvFrame中的data,转换为RGB的AvFragme中的数据
/**
* swsContext:转换上下文
* videoFrame->data:输入的数据
* videoFrame->linesize:一行的个数
* 0, videoContext->height:从0到视频的height
* rgbFrame->data:输出的数据放哪里
* rgbFrame->linesize:输出一行的像素
*/
sws_scale(swsContext, videoFrame->data, videoFrame->linesize, 0, videoContext->height,
rgbFrame->data, rgbFrame->linesize
);
//&windowBuffer是个入参出参对象,不用赋值,直接声明
//调用这个函数的时候我们就找到了ANativeWindow_Buffer
ANativeWindow_lock(nativeWindow, &windowBuffer, NULL);
//目的地,windowBuffer.bits转换成字节,是copy的目的地。
uint8_t *dstWindow = static_cast<uint8_t *>(windowBuffer.bits);
// 原始数据现在是outbuffer
// 一行一行的copy,不能整幅图copy,因为宽不一致
for (int i = 0; i < height; ++i) {
//直接把数据从outbuffer(RGB的AvFrame的data)拷贝到*dstWindow(bits)
/**
* dstWindow + i * windowBuffer.stride * 4 源数据的一行. windowBuffer.stride一行的像素,每个像素四个字节,所以乘以4
* rgbFrame->linesize[0] 一行的长度
*/
memcpy(dstWindow + i * windowBuffer.stride * 4, outbuffer + i * rgbFrame->linesize[0],
rgbFrame->linesize[0]);
}
ANativeWindow_unlockAndPost(nativeWindow);
av_frame_free(&videoFrame);
av_free(videoFrame);
videoFrame = NULL;
av_packet_free(&videoPacket);
av_free(videoPacket);
videoPacket = NULL;
}
return NULL;
}
void *decodePacket(void *pVoid) {
//运行再子线程中
LOGI("==========读取线程");
while (isStart) {
// 队列里面不能存放过多ACPacket,存放多了内存不够用,如果队列数据大于100,就休眠
if (videoQueue->size() > 100) {
usleep(100 * 1000);
}
//音频队列存储数据达到一定数量后也休眠
if (audioQueue->size() > 100) {
usleep(100 * 1000);
}
// avPacket.data是压缩数据
AVPacket *avPacket = av_packet_alloc();
// 读取数据,avFormatContext,总上下文;avPacket是个容器,相当于饭缸,打饭要有饭缸,读取数据要有avPacket
int ret = av_read_frame(avFormatContext, avPacket);//压缩数据
if (ret < 0) {
// 文件末尾
break;
}
if (avPacket->stream_index == videoIndex) {
//视频包
LOGD("视频包 %d", avPacket->size);
// 把数据包放到了视频队列中了,可以开始解码了。
videoQueue->push(avPacket);
} else if (avPacket->stream_index == audioIndex) {
LOGD("音频频包 %d", avPacket->size);
// 添加音频包
audioQueue->push(avPacket);
}
}
//一定要有返回值,没有会报错,void*与void不一样,void*有返回值,返回的是无符号类型
return NULL;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_maniu_maniuijk_MNPlayer_play(JNIEnv *env, jobject instance, jstring url_,
jobject surface) {
//把宽高传给Java层,解决视频图像变形的问题
jclass david_player = env->GetObjectClass(instance);
jmethodID onSizeChange = env->GetMethodID(david_player, "onSizeChange", "(II)V");
jmethodID createAudio = env->GetMethodID(david_player, "createTrack", "(II)V");
// 防止instance释放调,把instance变成全局的mInstance这样就不会释放掉。
mInstance = env->NewGlobalRef(instance);
const char *url = env->GetStringUTFChars(url_, 0);
// 初始化ffmpeg的网络模块,支持网络播放,没有的话就不能播放网络视频
avformat_network_init();
// 初始化总上下文
avFormatContext = avformat_alloc_context();
// 打开视频文件
// 参数1:总上下文
//参数2:url地址
// 第三、四个参数表示要获取什么信息
avformat_open_input(&avFormatContext, url, NULL, NULL);
// 文件可不可读,能不能用,就看能不能找到流
int code = avformat_find_stream_info(avFormatContext, NULL);
if (code < 0) {
env->ReleaseStringUTFChars(url_, url);
return;
}
// 流的个数
avFormatContext->nb_streams;
// 遍历流的个数,找到音频流和视频流的索引
for (int i = 0; i < avFormatContext->nb_streams; i++) {
//视频流对象 codecpar表示视频流对象的参数,codec_type解码器类型
if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
// 找到视频索引
videoIndex = i;
// 所有的参数,包括视频和音频,都封装到了AVCodecParameters
AVCodecParameters *parameters = avFormatContext->streams[i]->codecpar;
LOGI("视频%d", i);
// 打印宽高,看看是否一致
LOGI("宽度width:%d ", parameters->width);
LOGI("高度height:%d ", parameters->height);
LOGI("延迟时间video_delay :%d ", parameters->video_delay);
// 生成一个解码器, 这里不能写死,根据视频文件动态获取
AVCodec *dec = avcodec_find_decoder(parameters->codec_id);
// 根据解码器初始化解码器上下文
videoContext = avcodec_alloc_context3(dec);
// 解码器需要宽高等参数信息,把读取文件里面的参数信息,设置到新的上上下文
avcodec_parameters_to_context(videoContext, parameters);
// 打开解码器
avcodec_open2(videoContext, dec, 0);
} else if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
// 找到音频索引,与视频类似,不在注释
audioIndex = i;
AVCodecParameters *parameters = avFormatContext->streams[i]->codecpar;
AVCodec *dec = avcodec_find_decoder(parameters->codec_id);
audioContext = avcodec_alloc_context3(dec);
avcodec_parameters_to_context(audioContext, parameters);
avcodec_open2(audioContext, dec, 0);
LOGI("音频%d", i);
}
}
// 通过视频上下文得到宽高。
width = videoContext->width;
height = videoContext->height;
//把宽高传给Java层,解决视频图像变形的问题
env->CallVoidMethod(instance, onSizeChange, width, height);
env->CallVoidMethod(instance, createAudio, 44100,
av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO));
LOGI("音频---------------> ");
playTrack = env->GetMethodID(david_player, "playTrack", "([BI)V");
// 传入Java的surface,就会返回ANativeWindow对象
nativeWindow = ANativeWindow_fromSurface(env, surface);
// 给ANativeWindow对象设值,宽、高、显示格式
ANativeWindow_setBuffersGeometry(nativeWindow, width, height, WINDOW_FORMAT_RGBA_8888);
// 开始实例化线程
// 容器的大小只跟宽高和显示格式有关,
/**
* 1:对齐方式,一般是一个字节对齐
*/
int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, width, height, 1);
//实例化容器
outbuffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
LOGI("音频--------------->2 ");
// 初始化RGB的容器,rgbFrame初始化了,但里面的data还没初始化,还是0。
// 容器的大小只跟宽高和显示格式有关
rgbFrame = av_frame_alloc();
//不能直接这样赋值
// rgbFrame->data = malloc(numBytes) data是二维容器,这样赋值不知道宽在哪里换行
//因为是二维,所以不能这样填充rgbFrame->data = outbuffer,要用下面的方式填充
/**
* rgbFrame->data:填充给data
* rgbFrame->linesize:一行的大小
* outbuffer:把谁填充过去
* AV_PIX_FMT_RGBA填充的格式
* 1对其方式
*/
// rgbFrame->data的数据就是outbuffer,以后就可以从outbuffer中取到数据
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, outbuffer, AV_PIX_FMT_RGBA, width,
height, 1);
//转换器上下文,视频YUV转换为视频RGB需要转换器,转换器需要转换器上下文
//参数0-5:输入宽高格式,输出的宽高格式。videoContext->pix_fmt可以获得yuv的格式。
//参数SWS_BICUBIC:转换的算法
//后三个参数,输入的滤镜,输出的滤镜,最后一个参数我也不知道,传null就可以
swsContext = sws_getContext(width, height, videoContext->pix_fmt,
width, height, AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL, NULL, NULL);
LOGI("音频--------------->3 ");
// 初始化音视频队列
audioQueue = new MNQueue;
videoQueue = new MNQueue; //rgb 不支持yuv
pthread_t thread_decode;
pthread_t thread_vidio;
pthread_t thread_audio;
isStart = true;
// native函数,实例化线程
// 句柄,&thread_decode,类似于Java的Thread
//decodePacket ,类似于Java的run方法,很重要
pthread_create(&thread_decode, NULL, decodePacket, NULL);
pthread_create(&thread_vidio, NULL, decodeVideo, NULL);
// 解码音频线程
pthread_create(&thread_audio, NULL, decodeAudio, NULL);
env->ReleaseStringUTFChars(url_, url);
}
package com.maniu.maniuijk;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.view.Surface;
import android.view.SurfaceView;
import android.widget.RelativeLayout;
public class MNPlayer {
static {
System.loadLibrary("maniuijk");
}
SurfaceView surfaceView;
private AudioTrack audioTrack;
public MNPlayer(SurfaceView surfaceView) {
this.surfaceView = surfaceView;
}
//没有问题 鲜花
public native void play(String url, Surface surface);
//直播 rtm 协议 直播 万能
// 获取到视频的宽高
public void onSizeChange(int width, int heigth) {
//计算视频的宽高比
float ratio = width / (float) heigth;
// 得到屏幕宽
int screenWidth = surfaceView.getContext().getResources().getDisplayMetrics().widthPixels;
int videoWidth = 0;
int videoHeigth = 0;
videoWidth = screenWidth;
videoHeigth = (int) (screenWidth / ratio);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(videoWidth, videoHeigth);
surfaceView.setLayoutParams(lp); //调整surfaceview控件的大小
}
// 采样频率,底层传给我的,让底层在初始化的时候回调,调用native play的时候,底层读取通道数,采样频率等。会回调一次
public void createTrack(int sampleRateInHz,int nb_channals) {
int channaleConfig;//通道数
if (nb_channals == 1) {//1代表单通道
channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
} else if (nb_channals == 2) {//2代表双通道
channaleConfig = AudioFormat.CHANNEL_OUT_STEREO;
}else {
channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
}
int buffersize = AudioTrack.getMinBufferSize(sampleRateInHz,
channaleConfig, AudioFormat.ENCODING_PCM_16BIT);
/**
* AudioManager.STREAM_MUSIC 流类型。有闹钟,系统声音等可供选择
* sampleRateInHz 采样频率
* channaleConfig 采样通道数
* AudioFormat.ENCODING_PCM_16BIT 采样位数
* buffersize 每次播放多少,前三个确定了,这里就可以确定
* AudioTrack.MODE_STREAM 是文件还是流,我们这里是流类型
*/
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRateInHz, channaleConfig, AudioFormat.ENCODING_PCM_16BIT, buffersize, AudioTrack.MODE_STREAM);
audioTrack.play();
}
// 解码好数据回调,会回调N次
public void playTrack(byte[] buffer, int lenth) {//buffer实际是个波,把波写道buffer中
if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
//播放声音
audioTrack.write(buffer, 0, lenth);
}
}
}