美文网首页音视频
ffmpeg播放器开发 详细记录+代码实现2

ffmpeg播放器开发 详细记录+代码实现2

作者: 李星星星星星 | 来源:发表于2018-09-16 17:29 被阅读0次

请接上一个简书内容观看,会对上一章的代码进行改进和修改~~~

ffmpeg播放器2-视频解码与原生绘制

1.ffmpeg 解码视频并播放的开发

CMakeLists.txt中要加入  android库
实现  DNFFmpeg的 start()方法

2.相关知识整理

ANativeWindow

ANativeWindow代表的是本地窗口,可以看成NDK提供Native版本的Surface。通过ANativeWindow_fromSurface获得ANativeWindow指针,ANativeWindow_release进行释放。类似Java,可以对它进行lock、unlockAndPost以及通过ANativeWindow_Buffer进行图像数据的修改。

#include <android/native_window_jni.h>
//先释放之前的显示窗口
if (window) {
    ANativeWindow_release(window);
    window = 0;
}
//创建新的窗口用于视频显示
window = ANativeWindow_fromSurface(env, surface);
//设置窗口属性
ANativeWindow_setBuffersGeometry(window, w,
                                     h,
                                     WINDOW_FORMAT_RGBA_8888);

ANativeWindow_Buffer window_buffer;
if (ANativeWindow_lock(window, &window_buffer, 0)) {
    ANativeWindow_release(window);
    window = 0;
    return;
}
//填充rgb数据给dst_data
uint8_t *dst_data = static_cast<uint8_t *>(window_buffer.bits);
//......
ANativeWindow_unlockAndPost(window);

在NDK中使用ANativeWindow编译时需要链接NDK中的libandroid.so

#编译链接NDK/platforms/android-X/usr/lib/libandroid.so
target_link_libraries(XXX android )

3.代码实现:

java调用 native_lib.cpp 中的 natice_setSurface()方法,传入android中的surface
native_lib.cpp中调用 DNFFmpeg的start()方法进行解码和播放

...之前的创建类:
创建 DNPlayer.java,用于调用so中的方法
创建 mylog.h, 用于定义一些宏
创建 DNFFmpeg.h & .cpp ,用于提供方法给 native-lib.cpp 调用 
创建 JavaCallHelper.h & .cpp ,用于so调用java的方法(即回调)
创建 AudioChannel.h & .cpp ,用于音频开发
创建 VideoChannel& .cpp ,用于视频开发
... 新创建类:
safeQueue.h 用户创建线程安全的队列
BaseChannel.h 用于音频和视频开发类的父类,存放一些共用的方法和信息

1. DNPlayer.java:

在surfaceChanged回调中调用 native_setSurface(Surface surface)方法传入 Surface 
编写start()方法 ->调用 native_start()方法
...

2. BaseChannel.h

#ifndef MYFFMPEGPLAYER_BASECHANNEL_H
#define MYFFMPEGPLAYER_BASECHANNEL_H

extern "C"{
#include <libavcodec/avcodec.h>
};
#include "safe_queue.h"

class BaseChannel {
public:
   BaseChannel(int i,AVCodecContext *avCodecContext) : index(i),avCodecContext(avCodecContext) {
        // 设置释放队列中的AVPacket 对象数据的方法回调
        packets.setReleaseCallBack(BaseChannel::releaseAVPacket);
        // 设置释放队列中的 AVFrame 对象数据的方法回调
        frames.setReleaseCallBack(BaseChannel::releaseAVFrame);
    };

    virtual ~BaseChannel() {
        packets.clear();
        frames.clear();
    };
    // 释放 AVPacket
    static void releaseAVPacket(AVPacket*& packet){
        if(packet){
            av_packet_free(&packet);
            packet = 0;
        }
    }

    // 释放 AVFrame
    static void releaseAVFrame(AVFrame*& frame){
        if(frame){
            av_frame_free(&frame);
            frame = 0;
        }
    }


    // 抽象方法 解码+播放
    virtual void start() = 0;


    int index;
    // 线程安全的队列  用于存放压缩后的包
    SafeQueue<AVPacket*> packets;

    // 线程安全的队列  用于存放解码后的数据
    SafeQueue<AVFrame*> frames;

    // 解码器上下文
    AVCodecContext *avCodecContext = 0;

    bool isPlaying;// 是否工作
};

#endif //MYFFMPEGPLAYER_BASECHANNEL_H



3.safe_queue.h

#ifndef DNRECORDER_SAFE_QUEUE_H
#define DNRECORDER_SAFE_QUEUE_H

#include <queue>
#include <pthread.h>

using namespace std;
//线程安全的队列
template<typename T>
class SafeQueue {
    typedef void (*ReleaseCallBack)(T &);

    typedef void (*SyncHandle)(queue<T> &);

public:
    SafeQueue() {
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&cond, NULL);
    }

    ~SafeQueue() {
        pthread_cond_destroy(&cond);
        pthread_mutex_destroy(&mutex);
    }

    void push(const T new_value) {
        pthread_mutex_lock(&mutex);
        if (work) {
            q.push(new_value);
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
        }
        pthread_mutex_unlock(&mutex);
    }


    int pop(T &value) {
        int ret = 0;
        pthread_mutex_lock(&mutex);
        //在多核处理器下 由于竞争可能虚假唤醒 包括jdk也说明了
        while (work && q.empty()) {
            pthread_cond_wait(&cond, &mutex);
        }
        if (!q.empty()) {
            value = q.front();
            q.pop();
            ret = 1;
        }
        pthread_mutex_unlock(&mutex);
        return ret;
    }

    void setWork(int work) {
        pthread_mutex_lock(&mutex);
        this->work = work;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }

    int empty() {
        return q.empty();
    }

    int size() {
        return q.size();
    }

    void clear() {
        pthread_mutex_lock(&mutex);
        int size = q.size();
        for (int i = 0; i < size; ++i) {
            T value = q.front();
            releaseCallBack(value);
            q.pop();
        }
        pthread_mutex_unlock(&mutex);
    }

    void sync() {
        pthread_mutex_lock(&mutex);
        // 同步代码块 能在线程安全的背景下操作 queue :例如 主动丢包
        syncHandle(q);
        pthread_mutex_unlock(&mutex);
    }

    void setReleaseCallBack(ReleaseCallBack r) {
        releaseCallBack = r;
    }

    void setSyncHandle(SyncHandle s) {
        syncHandle = s;
    }

private:

    pthread_cond_t cond;
    pthread_mutex_t mutex;

    queue<T> q;
    // 是否工作  1工作  0不工作
    int work;
    ReleaseCallBack releaseCallBack;
    SyncHandle syncHandle;

};


#endif //DNRECORDER_SAFE_QUEUE_H

4.VideoChannel.h & VideoChannel.cpp
VideoChannel.h:

#ifndef MYFFMPEGPLAYER_VIDEOCHANNEL_H
#define MYFFMPEGPLAYER_VIDEOCHANNEL_H


#include "BaseChannel.h"
extern "C"{
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
typedef void (*RenderFrameCallback)(uint8_t *,int,int,int);
class VideoChannel : public BaseChannel{
public:
    VideoChannel(int i, AVCodecContext *avCodecContext);
    // 解码+播放
    void start();
    // 解码
    void decode();
    // 播放
    void play();

    void setRenderFrameCallBack(RenderFrameCallback callback);

private:
    // 解码线程
    pthread_t pid_decode;
    // 播放线程
    pthread_t pid_play;

    SwsContext *swsContext = 0;

    RenderFrameCallback callback;
};


#endif //MYFFMPEGPLAYER_VIDEOCHANNEL_H

VideoChannel.cpp:

#include "VideoChannel.h"

// 解码线程
void *task_decode(void* args){
    VideoChannel *videoChannel = static_cast<VideoChannel *>(args);
    videoChannel->decode();
    return 0;
}
// 播放线程
void *task_play(void* args){
    VideoChannel *videoChannel = static_cast<VideoChannel *>(args);
    videoChannel->play();
    return 0;
}

VideoChannel::VideoChannel(int i,AVCodecContext *avCodecContext) : BaseChannel(i,avCodecContext) {}


void VideoChannel::start(){
    isPlaying = 1;
    packets.setWork(1);
    frames.setWork(1);
    // 1.解码
    pthread_create(&pid_decode,NULL,task_decode,this);
    // 2.播放
    pthread_create(&pid_play,NULL,task_play,this);
}
// 解码
void VideoChannel::decode() {
    AVPacket *avPacket = 0;
    while (isPlaying){
        //取出一个数据包
        int ret = packets.pop(avPacket);
        if(!isPlaying){
            break;
        }
        if(!ret){
            continue;
        }
        // 把包丢给解码器
        ret = avcodec_send_packet(avCodecContext,avPacket);
        releaseAVPacket(avPacket);
        if(ret != 0){
            break;
        }
        AVFrame *avFrame = av_frame_alloc();
        // 从解码器中读取解码后的数据包
        ret = avcodec_receive_frame(avCodecContext,avFrame);
        if(ret == AVERROR(EAGAIN)){
            // 读取失败  需要重试
            continue;
        }else if(ret != 0){
            break;
        }

        frames.push(avFrame);
    }
    releaseAVPacket(avPacket);
}

// 播放
void VideoChannel::play() {
    //目标: RGBA
    swsContext = sws_getContext(
            avCodecContext->width, avCodecContext->height,avCodecContext->pix_fmt,
            avCodecContext->width, avCodecContext->height,AV_PIX_FMT_RGBA,
            SWS_BILINEAR,0,0,0);
    AVFrame* frame = 0;
    //指针数组
    uint8_t *dst_data[4];
    int dst_linesize[4];
    av_image_alloc(dst_data, dst_linesize,
                   avCodecContext->width, avCodecContext->height,AV_PIX_FMT_RGBA, 1);
    while (isPlaying){
        int ret = frames.pop(frame);
        if (!isPlaying){
            break;
        }
        //src_linesize: 表示每一行存放的 字节长度
        ret = sws_scale(swsContext, reinterpret_cast<const uint8_t *const *>(frame->data),
                  frame->linesize, 0,
                  avCodecContext->height,
                  dst_data,
                  dst_linesize);
        //回调出去进行播放
        callback(dst_data[0],dst_linesize[0],avCodecContext->width, avCodecContext->height);
        releaseAVFrame(frame);
    }
    av_freep(&dst_data[0]);
    releaseAVFrame(frame);
}


void VideoChannel::setRenderFrameCallBack(RenderFrameCallback callback) {
    this->callback = callback;
}

5.DNFFmpeg.h & DNFFmpeg.cpp
DNFFmpeg.h:

#ifndef MYFFMPEGPLAYER_DNFFMPEG_H
#define MYFFMPEGPLAYER_DNFFMPEG_H

#include "mylog.h"
#include <cstring>
#include <pthread.h>
#include "DNFFmpeg.h"
#include "JavaCallHelper.h"
#include "AudioChannel.h"
#include "VideoChannel.h"

extern  "C" {
#include <libavformat/avformat.h>
}

class DNFFmpeg {
public:
    DNFFmpeg(JavaCallHelper* callHelper,const char* dataSource);
    ~DNFFmpeg();
    // 播放器准备工作
    void prepare();
    // 线程中调用该方法,用于实现具体解码音视频代码
    void _prepare();

    // 播放
    void start();
    // 线程中调用该方法 用于实现具体的播放代码
    void _start();

    void setRenderFrameCallback(RenderFrameCallback callback);

private:
    // 音视频地址
    char *dataSource;
    // 解码线程
    pthread_t pid;
    // 解码器上下文
    AVFormatContext *formatContext = 0;
    // 播放线程
    pthread_t pid_start;

    // ...
    JavaCallHelper* callHelper = 0;
    AudioChannel *audioChannel = 0;
    VideoChannel *videoChannel = 0;
    //是否正在播放
    bool isPlaying = 0;
    // 开始播放的回调 解码器解码完成后调用 native_window
    RenderFrameCallback callback;
};


#endif //MYFFMPEGPLAYER_DNFFMPEG_H

DNFFmpeg.cpp:

#include "DNFFmpeg.h"

// 解码线程
void* task_prepare(void* args){
    DNFFmpeg *dnfFmpeg = static_cast<DNFFmpeg *>(args);
    //调用解码方法
    dnfFmpeg->_prepare();
    return 0;
}

// 播放线程
void* task_start(void* args){
    DNFFmpeg *dnfFmpeg = static_cast<DNFFmpeg *>(args);
    //调用解码方法
    dnfFmpeg->_start();
    return 0;
}

DNFFmpeg::DNFFmpeg(JavaCallHelper *callHelper, const char *dataSource){
    this->callHelper = callHelper;
    //防止 dataSource参数 指向的内存被释放
    this->dataSource = new char[strlen(dataSource)+1];
    strcpy(this->dataSource,dataSource);
}

DNFFmpeg::~DNFFmpeg() {
    //释放
    DELETE(dataSource);
    DELETE(callHelper);
}

void DNFFmpeg::prepare() {
    // 创建一个解码的线程
    pthread_create(&pid,NULL,task_prepare,this);
}

// 解码实现方法
void DNFFmpeg::_prepare() {
    // 初始化网络 让ffmpeg能够使用网络
    avformat_network_init();
    //1、打开媒体地址(文件地址、直播地址)
    // AVFormatContext  包含了 视频的 信息(宽、高等)
    formatContext = 0;
    //文件路径不对 手机没网
    int ret = avformat_open_input(&formatContext,dataSource,0,0);
    //ret不为0表示 打开媒体失败
    if(ret != 0){
        LOGFFE("打开媒体失败:%s",av_err2str(ret));
        callHelper->onPreError(THREAD_CHILD,FFMPEG_CAN_NOT_OPEN_URL);
        return;
    }
    //2、查找媒体中的 音视频流 (给 contxt里的 streams等成员赋)
    ret =  avformat_find_stream_info(formatContext,0);
    // 小于0 则失败
    if (ret < 0){
        LOGFFE("查找流失败:%s",av_err2str(ret));
        callHelper->onPreError(THREAD_CHILD,FFMPEG_CAN_NOT_FIND_STREAMS);
        return;
    }
    //nb_streams :几个流(几段视频/音频)
    for (int i = 0; i < formatContext->nb_streams; ++i) {
        //可能代表是一个视频 也可能代表是一个音频
        AVStream *stream = formatContext->streams[i];
        //包含了 解码 这段流 的各种参数信息(宽、高、码率、帧率)
        AVCodecParameters  *codecpar =  stream->codecpar;

        //无论视频还是音频都需要干的一些事情(获得解码器)
        // 1、通过 当前流 使用的 编码方式,查找解码器
        AVCodec *dec = avcodec_find_decoder(codecpar->codec_id);
        if(dec == NULL){
            LOGFFE("查找解码器失败:%s",av_err2str(ret));
            callHelper->onPreError(THREAD_CHILD,FFMPEG_FIND_DECODER_FAIL);
            return;
        }
        //2、获得解码器上下文
        AVCodecContext *context = avcodec_alloc_context3(dec);
        if(context == NULL){
            LOGFFE("创建解码上下文失败:%s",av_err2str(ret));
            callHelper->onPreError(THREAD_CHILD,FFMPEG_ALLOC_CODEC_CONTEXT_FAIL);
            return;
        }
        //3、设置上下文内的一些参数 (context->width)
//        context->width = codecpar->width;
//        context->height = codecpar->height;
        ret = avcodec_parameters_to_context(context,codecpar);
        //失败
        if(ret < 0){
            LOGFFE("设置解码上下文参数失败:%s",av_err2str(ret));
            callHelper->onPreError(THREAD_CHILD,FFMPEG_CODEC_CONTEXT_PARAMETERS_FAIL);
            return;
        }
        // 4、打开解码器
        ret = avcodec_open2(context,dec,0);
        if (ret != 0){
            LOGFFE("打开解码器失败:%s",av_err2str(ret));
            callHelper->onPreError(THREAD_CHILD,FFMPEG_OPEN_DECODER_FAIL);
            return;
        }
        //音频
        if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
            audioChannel = new AudioChannel(i,context);
        } else if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            videoChannel = new VideoChannel(i,context);
            // 设置播放的回调
            videoChannel->setRenderFrameCallBack(callback);
        }
    }
    //没有音视频  (很少见)
    if(!audioChannel && !videoChannel){
        LOGFFE("没有音视频");
        callHelper->onPreError(THREAD_CHILD,FFMPEG_NOMEDIA);
        return;
    }
    // 准备完了 通知java 你随时可以开始播放
    callHelper->onPrepare(THREAD_CHILD);
}



void DNFFmpeg::start() {
    //正在播放
    isPlaying = 1;
    if(videoChannel){
        // 设置 videoChannel工作状态
        videoChannel->start();
    }
    pthread_create(&pid_start,0,task_start,this);
}

void DNFFmpeg::_start() {
    // 1.读取媒体数据包(音视频数据包)
    int ret;
    while(isPlaying){
        // 创建一个AVPacket
        AVPacket *packet = av_packet_alloc();
        // 读取流数据并塞入 AVPacket
        ret = av_read_frame(formatContext,packet);
        // ret == 0 成功  其他  失败
        if(ret == 0){
            // 通过 packet->stream_index 判断 是音频还是视频
            // packet->stream_index 可以通过 解码循环中存放的序号做对比
            if(audioChannel && packet->stream_index == audioChannel->index){

            }else if(videoChannel && packet->stream_index == videoChannel->index){
                videoChannel->packets.push(packet);
            }
        }else if(ret == AVERROR_EOF){
            // 读取完成 但是还没有播放完

        }else{

        }
    }
    // 2.解码
}


void DNFFmpeg::setRenderFrameCallback(RenderFrameCallback callback) {
    this->callback = callback;
}

6. native_lib.cpp

#include <jni.h>
#include <string>
#include "DNFFmpeg.h"
#include <android/native_window_jni.h>

DNFFmpeg *ffmpeg = 0;
JavaVM *javaVm = 0;
ANativeWindow *window = 0;
// 静态创建 同步锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int JNI_OnLoad(JavaVM *vm, void *r) {
    javaVm = vm;
    return JNI_VERSION_1_6;
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_myplayer_org_DNPlayer_stringFromJNI(JNIEnv *env, jobject instance) {

    // TODO
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

void render(uint8_t *data,int linesize,int w,int h){
    pthread_mutex_lock(&mutex);
    if(!window){
        pthread_mutex_unlock(&mutex);
        return;
    }
    //设置窗口属性
    ANativeWindow_setBuffersGeometry(window, w,
                                     h,
                                     WINDOW_FORMAT_RGBA_8888);
    ANativeWindow_Buffer window_buffer;
    if (ANativeWindow_lock(window, &window_buffer, 0)) {
        ANativeWindow_release(window);
        window = 0;
        pthread_mutex_unlock(&mutex);
        return;
    }
    //填充rgb数据给dst_data
    uint8_t *dst_data = static_cast<uint8_t *>(window_buffer.bits);
    // stride:一行有多少个数据(RGBA)
    int dst_linesize = window_buffer.stride*4;

    for (int i = 0; i < window_buffer.height; ++i) {
        memcpy(dst_data + i*dst_linesize ,data + i*linesize , dst_linesize);
    }
    ANativeWindow_unlockAndPost(window);
    pthread_mutex_unlock(&mutex);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_myplayer_org_DNPlayer_native_1prepare(JNIEnv *env, jobject instance, jstring dataSource_) {
    const char *dataSource = env->GetStringUTFChars(dataSource_, 0);

    //创建播放器
    JavaCallHelper *helper =  new JavaCallHelper(javaVm, env, instance);
    ffmpeg = new DNFFmpeg(helper, dataSource);
    ffmpeg->setRenderFrameCallback(render);
    ffmpeg->prepare();
    env->ReleaseStringUTFChars(dataSource_, dataSource);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_myplayer_org_DNPlayer_native_1start(JNIEnv *env, jobject instance) {

    ffmpeg->start();

}

extern "C"
JNIEXPORT void JNICALL
Java_com_myplayer_org_DNPlayer_native_1setSurface(JNIEnv *env, jobject instance, jobject surface) {
    pthread_mutex_lock(&mutex);
    if(window){
        ANativeWindow_release(window);
        window = 0;
    }
    window = ANativeWindow_fromSurface(env,surface);
    pthread_mutex_unlock(&mutex);
}

重点 DNFFmpeg 中的 _start()方法,VideoChannel中的 start()方法 以及 native_lib.cpp中的 render函数

相关文章

网友评论

    本文标题:ffmpeg播放器开发 详细记录+代码实现2

    本文链接:https://www.haomeiwen.com/subject/irytnftx.html