ffmpeg推流到ossrs

来源:互联网 发布:centos 7网络配置 编辑:程序博客网 时间:2024/05/22 04:52

首先感谢一下逝者,同时提醒广大码农注意身体,本文转自雷霄骅的csdn博客

http://blog.csdn.net/leixiaohua1020


FFmpeg

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


微信使用的动态链接库

FFmpeg 在Android中的使用

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


编译好的so库
FFmpeg主要分为8大模块:
  • libavcodec:用于各种类型声音/图像编解码;
  • libavdevice:用于视频采集访问摄像头等
  • libavfilter:用做滤镜处理
  • libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能;
  • libswresample:用于重采样;
  • libavutil:包含一些公共的工具函数;
  • libswscale:用于视频场景比例缩放、色彩映射转换;
  • libpostproc:用于后期效果处理;

    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播放一个文件的流程



解码流程图

函数简单介绍

  • av_register_all():注册所有组件。
  • avformat_open_input():打开输入视频文件。
  • avformat_find_stream_info():获取视频文件信息。
  • avcodec_find_decoder():查找解码器。
  • avcodec_open2():打开解码器。
  • av_read_frame():从输入文件读取一帧压缩数据。
  • avcodec_decode_video2():解码一帧压缩数据。
  • avcodec_close():关闭解码器。
  • avformat_close_input():关闭输入视频文件