王学岗————FFmpeg软解之视频播放(对应第39、第41、第42)

前言

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);
        }


    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值