废话不多说,直接上干货。
集成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