FFmpegH264/FFmpegffmpeg

简析Android集成FFMPEG推流

2016-09-04  本文已影响2405人  Loofer

如今,视频直播行业在中国算是比较热门。在刚刚过去的2015年,视频直播成为互联网行业最抢眼的领域之一。从游戏到秀场,从传统的网页端到移动互联网,各大直播平台包括斗鱼、熊猫tv、虎牙战旗还有纯移动端的映客、易直播等,群雄割据。言归正转,今天我们讲解的是Android 端简要集成FFMPEG进行推流。

推流基本流程

推流基本流程

一般情况下推流的基本流程如上。

FFmpeg

说到推流就不得不提大名鼎鼎的FFMPEG了,FFmpeg是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用LGPL或GPL许可证(依据你选择的组件)。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多codec都是从头开发的。诸如微信的小视频,映客、斗鱼等均是在FFmpeg基础上开发而来,可见FFmpeg之强大。一下是解压微信的apk的截图。

微信使用的动态链接库

FFmpeg 在Android中的使用

FFmpeg要想在Android就必须要编译成Android上能使用so动态链接库(下称so库)。具体编译方法可以参考github官方提供源码。编译过程大约在30分钟左右。以下是笔者编译好的so库。


编译好的so库
FFmpeg主要分为8大模块:

JNI开发调用FFmpeg

我们先看看我们使用到的FFmpegUtils工具类,主要用于加载native方法:

public class FFmpegUtils {

//加载so库
static{
      System.loadLibrary("avutil-54");
      System.loadLibrary("swresample-1");
      System.loadLibrary("avcodec-56");
      System.loadLibrary("avformat-56");
      System.loadLibrary("swscale-3");
      System.loadLibrary("postproc-53");
      System.loadLibrary("avfilter-5");
      System.loadLibrary("avdevice-56");
      System.loadLibrary("sffstreamer");
}
     /*
      * 把本地的音视频数据通过rtmp协议发送到流媒体服务器上
      *@param input 
      *@param output
      */
     public native int stream(String input, String output);

}

接下来我们就是我们的重头戏JNI调用FFmpeg

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/log.h"

#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__)
#define LOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, "(^_^)", format, ##__VA_ARGS__)
JNIEXPORT jint JNICALL Java_org_loofer_ffmpegstreamer_MainActivity_stream  (JNIEnv *env, jobject obj, jstring input_jstr, jstring output_jstr)
{


    //java string -> c char*
    //视频文件所在路径
    const char* input_cstr = (*env)->GetStringUTFChars(env,input_jstr, NULL));
    //推送的流媒体地址
    const char* output_cstr = (*env)->GetStringUTFChars(env,output_jstr, NULL));
    //封装格式(读入,写出)(解封装,得到frame)
    AVFormatContext *inFmtCtx = NULL, *outFmtCtx = NULL;
    AVOutputFormat *ofmt = NULL;
    
    AVPacket pkt;


    //注册组件
    av_register_all();
    //初始化网络
    avformat_network_init();

    //Input
    if ((ret = avformat_open_input(&inFmtCtx, input_str, 0, 0)) < 0) {
        LOGE( "Could not open input file.");
        goto end;
    }
    //获取文件信息
    if ((ret = avformat_find_stream_info(inFmtCtx, 0)) < 0) {
        LOGE( "Failed to retrieve input stream information");
        goto end;
    }
    //获取视频的索引位置
    int videoindex=-1;
    for(i=0; i<inFmtCtx->nb_streams; i++) 
        if(inFmtCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
            videoindex=i;
            break;
        }
    //输出封装格式,推送flv封装格式的视频流
    avformat_alloc_output_context2(&outFmtCtx, NULL, "flv",output_cstr); //RTMP
    //avformat_alloc_output_context2(&outFmtCtx, NULL, "mpegts", output_cstr);//UDP

    if (!outFmtCtx) {
        LOGE( "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }
    
    
    for (i = 0; i < inFmtCtx->nb_streams; i++) {
        //解码器,解码上下文保持一致
        AVStream *in_stream = inFmtCtx->streams[i];
        AVStream *out_stream = avformat_new_stream(outFmtCtx, in_stream->codec->codec);
        if (!out_stream) {
            LOGE( "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }
        //复制解码器上下文的 设置
        ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
        if (ret < 0) {
            LOGE( "Failed to copy context from input to output stream codec context\n");
            goto end;
        }
        //全局的header
        out_stream->codec->codec_tag = 0;
        if (outFmtCtx->oformat->flags & AVFMT_GLOBALHEADER)
            out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }
    //打开输出的AVIOContext IO流上下文
    ofmt = outFmtCtx->oformat;
    //Open output URL
    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&outFmtCtx->pb, output_cstr, AVIO_FLAG_WRITE);
        if (ret < 0) {
            LOGE( "Could not open output URL '%s'", output_cstr);
            goto end;
        }
    }
    //先写一个头
    ret = avformat_write_header(outFmtCtx, NULL);
    if (ret < 0) {
        LOGE( "Error occurred when opening output URL\n");
        goto end;
    }

    
    int frame_index=0;
    int64_t start_time=av_gettime();
    while (1) {
        AVStream *in_stream, *out_stream;
        //Get an AVPacket
        ret = av_read_frame(inFmtCtx, &pkt);
        if (ret < 0)
            break;
        //FIX:No PTS (Example: Raw H.264)
        //raw stream 裸流
        //PTS:Presentation Time Stamp 解码后视频帧要在什么时候取出来
        //DTS:送入解码器后什么时候标识进行解码
        if(pkt.pts==AV_NOPTS_VALUE){
            //Write PTS
            AVRational time_base1=inFmtCtx->streams[videoindex]->time_base;
            //Duration between 2 frames (us)
            int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(inFmtCtx->streams[videoindex]->r_frame_rate);
            //Parameters
            pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
            pkt.dts=pkt.pts;
            pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
        }
        //读入速度比较快,可以在这里调整读取速度减轻服务器压力
        if(pkt.stream_index==videoindex){
            AVRational time_base=inFmtCtx->streams[videoindex]->time_base;
            AVRational time_base_q={1,AV_TIME_BASE};
            int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
            int64_t now_time = av_gettime() - start_time;
            if (pts_time > now_time)
                av_usleep(pts_time - now_time);

        }

        in_stream  = inFmtCtx->streams[pkt.stream_index];
        out_stream = outFmtCtx->streams[pkt.stream_index];
        /* copy packet */
        //Convert PTS/DTS
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        //Print to Screen
        if(pkt.stream_index==videoindex){
            LOGE("Send %8d video frames to output URL\n",frame_index);
            frame_index++;
        }
        //写数据
        //ret = av_write_frame(outFmtCtx, &pkt);
        ret = av_interleaved_write_frame(outFmtCtx, &pkt);

        if (ret < 0) {
            LOGE( "Error muxing packet\n");
            break;
        }
        av_free_packet(&pkt);
        
    }
    //写结尾
    av_write_trailer(outFmtCtx);
end:
    //释放自愿
    avformat_close_input(&inFmtCtx);
    /* 关闭输出流 */
    if (outFmtCtx && !(ofmt->flags & AVFMT_NOFILE))
        avio_close(outFmtCtx->pb);
    avformat_free_context(outFmtCtx);
    if (ret < 0 && ret != AVERROR_EOF) {
        LOGE( "Error occurred.\n");
        return -1;
    }
    return 0;
}

为了更好的理解整个推流过程,最后放几张图

FFmpeg播放一个文件的流程 解码流程图

函数简单介绍

上一篇下一篇

猜你喜欢

热点阅读