Android使用FFmeg进行音视频解码,实时回调H264流到java接口中

废话不多说,直接上干货。

集成FFmpeg这里就不介绍了,这里以成功编译打包FFmpeg或者集成FFmpeg成功为基础

下面FFDemux.cpp文件是实现功能的主要代码逻辑:

视频流转码功能是线程阻塞的,这里没有在C++代码中实现线程,需要外层程序使用时自己实现线程管理。

启动此功能需要首先设置视频流地址,然后设置转码成功后的视频流的接收回调,最后是调用start方法启动视频流转码服务。这里需要上层确认视频流地址及视频流接收回调的准确性,c++不做处理。

注意:回调中视频流信息data,在初始化错误的时候会出现为null的情况,需要上层注意容错。


extern "C"
{
#include "FFDemux.h"
#include "include/libavformat/avformat.h"
#include "include/libavutil/avutil.h"
#include "include/libavutil/frame.h"
#include <android/log.h>
}
#define LOG_TAG "C_TAG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)

FFDemux::FFDemux() {
    m_DecoderState = 0 ;
}
StreamDataCallback mStreamDataCallback;
//设置流数据回调监听
int SetStreamDataCallback(StreamDataCallback streamDataCallback)
{
    mStreamDataCallback = streamDataCallback;
    return 0;
}


void start() {
    LOGD("FFDemux::start");
    m_DecoderState = STATE_DEMUXING;
    do {
        if(InitDemux() != 0) {
            break;
        }
        DemuxLoop();
    } while (false);

    DeInitDemux();
}

void stop() {
    LOGD("FFDemux::Stop2222");
    m_DecoderState = STATE_STOP;
}

int init(const char *url) {
    m_Url = url;
    return 0;
}

int InitDemux() {
    LOGD("FFDemux::InitDemux");
    int result = -1;
    do {
        //1.创建封装格式上下文
        m_AVFormatContext = avformat_alloc_context();
        AVDictionary *options = nullptr;
        av_dict_set(&options,"rtsp_transport", "tcp", 0);
        av_dict_set(&options,"stimeout","20000000",0);
        // 设置“buffer_size”缓存容量
        av_dict_set(&options, "buffer_size", "1024000", 0);
        av_dict_set(&options, "max_delay", "30000000", 0);
        //2.打开文件
        int re = avformat_open_input(&m_AVFormatContext, m_Url, nullptr, &options);
        if(re != 0)
        {
            LOGD("FFDemux::InitDemux avformat_open_input fail.  %d",re);
            break;
        }

        //3.获取音视频流信息
        if(avformat_find_stream_info(m_AVFormatContext, nullptr) < 0) {
            LOGD("FFDemux::InitDemux avformat_find_stream_info fail.");
            break;
        }

        //4.获取音视频流索引
        for(int i=0; i < m_AVFormatContext->nb_streams; i++) {
            if(m_AVFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                m_StreamIndex = i;
                break;
            }
        }

        if(m_StreamIndex == -1) {
            LOGD("FFDemux::InitFFDecoder Fail to find stream index.");
            break;
        }

        result = 0;

        m_Packet = av_packet_alloc();

    } while (false);

    (*mStreamDataCallback)(nullptr, 0, 0, STATE_ERROR);

    return result;
}

void DemuxLoop() {
    {
        m_DecoderState = STATE_DEMUXING;
    }
    LOGD("FFDemux::DemuxLoop");

    for(;;) {
        LOGD("111111111111111111111111111   m_DecoderState =%d", m_DecoderState);
        if(m_DecoderState == STATE_STOP) {
            break;
        }

        if(DecodeOnePacket() != 0) {
            //解码结束,暂停解码器
            m_DecoderState = STATE_ERROR;
        }
    }
}

int DecodeOnePacket() {
    LOGD("FFDemux::DecodeOnePacket");
    int result = av_read_frame(m_AVFormatContext, m_Packet);
    while(result == 0) {
        if(m_DecoderState == STATE_STOP)
        {
            LOGD("111111111111111111111111111  m_DecoderState is stop");
            break;
        }
        if(m_Packet->stream_index == m_StreamIndex) {
            OnReceivePacket(m_Packet);
            LOGD("111111111111111111111111111   BaseDecoder::DecodeOnePacket_Ex packet size =%d", m_Packet->size);
            //判断一个 packet 是否解码完成
        }
        av_packet_unref(m_Packet);
        result = av_read_frame(m_AVFormatContext, m_Packet);
    }

    av_packet_unref(m_Packet);
    return result;
}

void DeInitDemux() {
    LOGD("FFDemux::DeInitDemux");

    if(m_Packet != nullptr) {
        av_packet_free(&m_Packet);
        m_Packet = nullptr;
    }

    if(m_AVFormatContext != nullptr) {
        avformat_close_input(&m_AVFormatContext);
        avformat_free_context(m_AVFormatContext);
        m_AVFormatContext = nullptr;
    }

}

void OnReceivePacket(AVPacket *packet) {
    LOGD("FFDemux::OnReceivePacket");
    (*mStreamDataCallback)((char *)(packet->data), packet->size, packet->pts, m_DecoderState);
}

接下来使用JNA来调用此功能

创建FFmpegTranscodeSDKJNA.java文件


    /**
     * 解码库中FFDemux模块
     * 解析的流数据回调接口
     * data 流数据
     * state 状态 0=正常;1=停止;2=失败
     */
    interface StreamDataCallback extends Callback{
        int invoke(ByteByReference data, int iDataLen, long pts, int state);
    }


    /**
     * 解码库中FFDemux模块
     * 设置流数据回调监听接口
     * @param streamDataCallback
     * @return
     */
    int SetStreamDataCallback(StreamDataCallback streamDataCallback);

    /**
     * 解码库中FFDemux模块
     * 初始化
     * @param url  流地址
     * @return
     */
    int init(String url);
    /**
     * 解码库中FFDemux模块
     * 开始取流转码
     * @return
     */
    int start();
    /**
     * 解码库中FFDemux模块
     * 停止取流转码
     * @return
     */
    int stop();

为了方便使用,创建FFmpegTranscodeManager.java文件来调用转码功能


    private StreamDataCallback mStreamDataCallback;

    private Thread thread;
    public FFmpegTranscodeManager() {
        initAll();
    }

    private static FFmpegTranscodeManager netSdk = null;
    /**
     * get the instance of JNGB28181SDK
     * @return the instance of JNGB28181SDK
     */
    public static FFmpegTranscodeManager getInstance()
    {
        if (null == netSdk)
        {
            synchronized (FFmpegTranscodeManager.class)
            {
                netSdk = new FFmpegTranscodeManager();
            }
        }
        return netSdk;
    }

    /**
     * 设置流数据回调监听接口
     * @param streamDataCallback
     */
    public void SetStreamDataCallback(StreamDataCallback streamDataCallback){
        mStreamDataCallback = streamDataCallback;
    }

    /**
     * 初始化所有的监听接口
     * SDK这里做一层防护,防止对接SDK方实现时接口设置问题导致的c库报错
     */
    public void initAll(){
//        设置流数据回调监听接口
        FFmpegTranscodeSDKJNAInstance.getInstance().SetStreamDataCallback(new FFmpegTranscodeSDKJNA.StreamDataCallback() {
            @Override
            public int invoke(ByteByReference data, int iDataLen, long pts, int state) {
                try {
                    if (null!=mStreamDataCallback){
                        if (0==state)return mStreamDataCallback.onData(
                                data.getPointer().getByteArray(0, iDataLen), iDataLen, pts, state);
                        else mStreamDataCallback.onFail(state);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                return 0;
            }
        });
        
    }

    /**
     * 解码库中FFDemux模块
     * 初始化
     * @param url  流地址
     * @return
     */
    public int Init(String url){
        return FFmpegTranscodeSDKJNAInstance.getInstance().init(url);
    }
    /**
     * 解码库中FFDemux模块
     * 开始取流转码
     * @return
     */
    public int Start(){
        return FFmpegTranscodeSDKJNAInstance.getInstance().start();
    }
    /**
     * 解码库中FFDemux模块
     * 停止取流转码
     * @return
     */
    public int Stop(){
        return FFmpegTranscodeSDKJNAInstance.getInstance().stop();
    }

    /**
     * 解码库中FFDemux模块
     * 线程中开启转码功能
     * @param url
     * @param streamDataCallback
     */
    public void startFFDemuxTranscode(String url, StreamDataCallback streamDataCallback){
        long time = 10;
        if (null!=thread ){
            try {
                Stop();
                time = 500;
                thread = null;
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Init(url);
                SetStreamDataCallback(streamDataCallback);
                Start();
            }
        });
        (new Handler()).postDelayed(new Runnable() {
            @Override
            public void run() {
                if (null!=thread)thread.start();
            }
        }, time);

    }

最后功能使用时

String url = binding.etDemuxUrl.getText().toString();
                if (TextUtils.isEmpty(url)){
                    url = "rtmp://58.200.131.2:1935/livetv/cctv1";
                }
                FFmpegTranscodeManager.getInstance().startFFDemuxTranscode(url, new StreamDataCallback() {
                    @Override
                    public int onData(byte[] data, int iDataLen, long pts, int state) {
                        new Handler(Looper.getMainLooper()).post(new Runnable() {
                            @Override
                            public void run() {
                                binding.tvVideoInfo.setText("视频信息:iDataLen=" + iDataLen
                                        + " pts=" + pts + "  data=" + data[0]+"-"+data[1]+"-"+data[2]+"-"+data[3]+"-"+data[4] );
                            }
                        });
                        return 0;
                    }
                    @Override
                    public void onFail(int state) {

                    }
                });

完整项目下载地址:https://download.csdn.net/download/hh7181521/87064921

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ffmpeg硬件解码demo主要是通过调用ffmpeg库接口来实现硬件加速解码功能。下面是一个简单的示例代码: ```c++ #include <stdio.h> #include <stdbool.h> #include <SDL2/SDL.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/imgutils.h> int main(int argc, char* argv[]) { // 初始化FFmpeg库 av_register_all(); avformat_network_init(); // 打开视频文件 AVFormatContext* formatCtx = avformat_alloc_context(); if (avformat_open_input(&formatCtx, argv[1], NULL, NULL) != 0) { printf("无法打开输入文件\n"); return -1; } if (avformat_find_stream_info(formatCtx, NULL) < 0) { printf("无法获取流信息\n"); return -1; } // 查找视频流 int videoStreamIndex = -1; for (int i = 0; i < formatCtx->nb_streams; i++) { if (formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } if (videoStreamIndex == -1) { printf("无法找到视频流\n"); return -1; } // 获取解码器 AVCodec* codec = avcodec_find_decoder(formatCtx->streams[videoStreamIndex]->codecpar->codec_id); if (codec == NULL) { printf("找不到解码器\n"); return -1; } // 打开解码器 AVCodecContext* codecCtx = avcodec_alloc_context3(codec); if (avcodec_parameters_to_context(codecCtx, formatCtx->streams[videoStreamIndex]->codecpar) != 0) { printf("无法打开解码器\n"); return -1; } if (avcodec_open2(codecCtx, codec, NULL) < 0) { printf("无法打开解码器\n"); return -1; } // 创建SDL窗口 SDL_Window* window = SDL_CreateWindow("FFmpeg硬件解码Demo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, codecCtx->width, codecCtx->height, SDL_WINDOW_OPENGL); if (window == NULL) { printf("无法创建窗口\n"); return -1; } // 创建SDL渲染器 SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0); AVFrame* frame = av_frame_alloc(); AVPacket packet; // 读取视频帧并渲染 bool quit = false; while (!quit) { if (av_read_frame(formatCtx, &packet) < 0) { break; } if (packet.stream_index == videoStreamIndex) { avcodec_send_packet(codecCtx, &packet); while (avcodec_receive_frame(codecCtx, frame) == 0) { SDL_RenderClear(renderer); // 将帧数据复制到纹理 SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, codecCtx->width, codecCtx->height); SDL_UpdateYUVTexture(texture, NULL, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); SDL_DestroyTexture(texture); SDL_Event event; SDL_PollEvent(&event); if (event.type == SDL_QUIT) { quit = true; break; } } } av_packet_unref(&packet); } // 释放资源 av_frame_free(&frame); avcodec_close(codecCtx); avformat_close_input(&formatCtx); return 0; } ``` 这个示例代码使用了SDL库来进行窗口和渲染器的创建,通过SDL播放解码后的视频帧。其,ffmpeg库提供了相关的接口方法来实现视频文件的打开、流信息的获取、解码器的查找和打开、帧的解码等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hh7181521

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值