zxj9487

Android 推流

配置RMTP服务器,虽然之前说了,这里就直接粘贴过来吧

1.配置RTMP服务器

这个我不多说贴两个博客分别是在mac和windows环境上的,大家跟着弄
MAC搭建RTMP服务器
https://www.jianshu.com/p/6fcec3b9d644
这个是在windows上的,RTMP服务器搭建(crtmpserver和nginx)

https://www.jianshu.com/p/c71cc39f72ec

2.关于推流输出的ip地址我好好说说


我这里是手机开启热点,电脑连接手机,这个RTMP服务器的推流地址有localhost,服务器在电脑上,对于电脑这个localhost是127.0.0.1,但是对于外界比如手机,你不能用localhost,而是用这个电脑的在这个热点也就是局域网的ip地址,不是127.0.0.1这个只代表本设备节点的ip地址,这个你需要去手机设置——》更多——》移动网络共享——》便携式WLAN热点——》管理设备列表,就可以看到电脑的局域网ip地址了

3.代码

我们这里要用到SurfaceView和Camera这对老组合,多而不说,就是Camera的配置有的需要注意

                        Camera.Parameters parameters = camera.getParameters();
                        //对拍照参数进行设置
                        for (Camera.Size size : parameters.getSupportedPictureSizes()) {
                            LogUtils.d(size.width + "  " + size.height);
                        }

 

注意这段打印出来的宽高,后来设置Camera拍摄的图片大小配置必须是里面的一组,否则无法获取Camera的回调数据,这个很关键。

parameters.setPictureSize(screenWidth, screenHeight); // 设置照片的大小

还有cpp文件里的宽高也要这样,否则程序会崩溃,其实这里的宽高我们可以通过比例缩放来处理,就可以任意使用宽高,但是我这里没有写。。。。。。。。。

int width = 320;
int height = 240;

Camera预览回调

camera.setPreviewCallback(new StreamIt()); // 设置回调的类

我们在这个回调里传送需要进行推流的数据,这里通过isPlaying标识符控制了,需要我们点击start按钮才会开始推流,并且这里传输数据的代码是通过开启一个单线程来完成,保证上次操作完成了才会执行下一次。

public class StreamIt implements Camera.PreviewCallback {
        @Override
        public void onPreviewFrame(final byte[] data, Camera camera) {
            if(isPlaying){
                long endTime = System.currentTimeMillis();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        encodeTime = System.currentTimeMillis();
                        FFmpegHandle.getInstance().onFrameCallback(data);
                        LogUtils.w("编码第:" + (encodeCount++) + "帧,耗时:" + (System.currentTimeMillis() - encodeTime));
                    }
                });
                LogUtils.d("采集第:" + (++count) + "帧,距上一帧间隔时间:"
                        + (endTime - previewTime) + "  " + Thread.currentThread().getName());
                previewTime = endTime;
            }
 
        }
    }

之前还执行了initVideo函数,初始化了FFmpeg并传输了推流地址

计算编码出的yuv数据的大小

    yuv_width = width;
    yuv_height = height;
    y_length = width * height;
    uv_length = width * height / 4;

初始化组件和输出编码环境

    av_register_all();
 
    //output initialize
    avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_path);
    //output encoder initialize
    pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!pCodec) {
        loge("Can not find encoder!\n");
        return -1;
    }

配置编码环境

    pCodecCtx = avcodec_alloc_context3(pCodec);
    //编码器的ID号,这里为264编码器,可以根据video_st里的codecID 参数赋值
    pCodecCtx->codec_id = pCodec->id;
    //像素的格式,也就是说采用什么样的色彩空间来表明一个像素点
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    //编码器编码的数据类型
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    //编码目标的视频帧大小,以像素为单位
    pCodecCtx->width = width;
    pCodecCtx->height = height;
    pCodecCtx->framerate = (AVRational) {fps, 1};
    //帧率的基本单位,我们用分数来表示,
    pCodecCtx->time_base = (AVRational) {1, fps};
    //目标的码率,即采样的码率;显然,采样码率越大,视频大小越大
    pCodecCtx->bit_rate = 400000;
    //固定允许的码率误差,数值越大,视频越小
//    pCodecCtx->bit_rate_tolerance = 4000000;
    pCodecCtx->gop_size = 50;
    /* Some formats want stream headers to be separate. */
    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
        pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
 
    //H264 codec param
//    pCodecCtx->me_range = 16;
    //pCodecCtx->max_qdiff = 4;
    pCodecCtx->qcompress = 0.6;
    //最大和最小量化系数
    pCodecCtx->qmin = 10;
    pCodecCtx->qmax = 51;
    //Optional Param
    //两个非B帧之间允许出现多少个B帧数
    //设置0表示不使用B帧
    //b 帧越多,图片越小
    pCodecCtx->max_b_frames = 0;
 
    if (pCodecCtx->codec_id == AV_CODEC_ID_H264) {
//        av_dict_set(¶m, "preset", "slow", 0);
        /**
         * 这个非常重要,如果不设置延时非常的大
         * ultrafast,superfast, veryfast, faster, fast, medium
         * slow, slower, veryslow, placebo. 这是x264编码速度的选项
       */
        av_dict_set(¶m, "preset", "superfast", 0);
        av_dict_set(¶m, "tune", "zerolatency", 0);
    }

打开编码器

    if (avcodec_open2(pCodecCtx, pCodec, ¶m) < 0) {
        loge("Failed to open encoder!\n");
        return -1;
    }

创建并配置一个视频流

    video_st = avformat_new_stream(ofmt_ctx, pCodec);
    if (video_st == NULL) {
        return -1;
    }
    video_st->time_base.num = 1;
    video_st->time_base.den = fps;
//    video_st->codec = pCodecCtx;
    video_st->codecpar->codec_tag = 0;
    avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);

查看输出url是否有效,并根据输出格式写入文件头

    if (avio_open(&ofmt_ctx->pb, out_path, AVIO_FLAG_READ_WRITE) < 0) {
        loge("Failed to open output file!\n");
        return -1;
    }
    //Write File Header
    avformat_write_header(ofmt_ctx, NULL);

接下来就是处理Camera传送过来的数据

转换数据格式

 jbyte *in = env->GetByteArrayElements(buffer_, NULL);

根据编码器获取缓存图片大小,并创建缓存图片空间

    int picture_size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width,
                                                pCodecCtx->height, 1);
    uint8_t *buffers = (uint8_t *) av_malloc(picture_size);

将之前创建的缓存图片空间赋予AVFrame

    pFrameYUV = av_frame_alloc();
    //将buffers的地址赋给AVFrame中的图像数据,根据像素格式判断有几个数据指针
    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, buffers, pCodecCtx->pix_fmt,
                         pCodecCtx->width, pCodecCtx->height, 1);

转换AVFrame格式,卓摄像头数据为NV21格式,此处将其转换为YUV420P格式

    memcpy(pFrameYUV->data[0], in, y_length); //Y
    pFrameYUV->pts = count;
    for (int i = 0; i < uv_length; i++) {
        //将v数据存到第三个平面
        *(pFrameYUV->data[2] + i) = *(in + y_length + i * 2);
        //将U数据存到第二个平面
        *(pFrameYUV->data[1] + i) = *(in + y_length + i * 2 + 1);
    }
 
    pFrameYUV->format = AV_PIX_FMT_YUV420P;
    pFrameYUV->width = yuv_width;
    pFrameYUV->height = yuv_height;

编码AVFrame数据

avcodec_send_frame(pCodecCtx, pFrameYUV);

获取编码后得到的数据

avcodec_receive_packet(pCodecCtx, &enc_pkt);

释放AVFrame

av_frame_free(&pFrameYUV);

对编码后的数据进行配置,设置播放时间等

    enc_pkt.stream_index = video_st->index;
    AVRational time_base = ofmt_ctx->streams[0]->time_base;//{ 1, 1000 };
    enc_pkt.pts = count * (video_st->time_base.den) / ((video_st->time_base.num) * fps);
    enc_pkt.dts = enc_pkt.pts;
    enc_pkt.duration = (video_st->time_base.den) / ((video_st->time_base.num) * fps);
    __android_log_print(ANDROID_LOG_WARN, "eric",
                        "index:%d,pts:%lld,dts:%lld,duration:%lld,time_base:%d,%d",
                        count,
                        (long long) enc_pkt.pts,
                        (long long) enc_pkt.dts,
                        (long long) enc_pkt.duration,
                        time_base.num, time_base.den);
    enc_pkt.pos = -1;

进行推流

av_interleaved_write_frame(ofmt_ctx, &enc_pkt);

释放Camera传输过来的数据

env->ReleaseByteArrayElements(buffer_, in, 0);

最后释放所有资源

    if (video_st)
        avcodec_close(video_st->codec);
    if (ofmt_ctx) {
        avio_close(ofmt_ctx->pb);
        avformat_free_context(ofmt_ctx);
        ofmt_ctx = NULL;
    }

4.VLC的使用

在进行推流时,输入推流地址,观看推流数据

转载:https://blog.csdn.net/z979451341/article/details/79398647

源码地址
https://github.com/979451341/RtmpCamera/tree/master

源码地址
https://github.com/979451341/RtmpCamera/tree/master

配置RMTP服务器,虽然之前说了,这里就直接粘贴过来吧

1.配置RTMP服务器


这个我不多说贴两个博客分别是在mac和windows环境上的,大家跟着弄
MAC搭建RTMP服务器
https://www.jianshu.com/p/6fcec3b9d644
这个是在windows上的,RTMP服务器搭建(crtmpserver和nginx)

https://www.jianshu.com/p/c71cc39f72ec

2.关于推流输出的ip地址我好好说说


我这里是手机开启热点,电脑连接手机,这个RTMP服务器的推流地址有localhost,服务器在电脑上,对于电脑这个localhost是127.0.0.1,但是对于外界比如手机,你不能用localhost,而是用这个电脑的在这个热点也就是局域网的ip地址,不是127.0.0.1这个只代表本设备节点的ip地址,这个你需要去手机设置——》更多——》移动网络共享——》便携式WLAN热点——》管理设备列表,就可以看到电脑的局域网ip地址了


3.代码

 

我们这里要用到SurfaceView和Camera这对老组合,多而不说,就是Camera的配置有的需要注意

  1.  
    Camera.Parameters parameters = camera.getParameters();
  2.  
    //对拍照参数进行设置
  3.  
    for (Camera.Size size : parameters.getSupportedPictureSizes()) {
  4.  
    LogUtils.d(size.width + " " + size.height);
  5.  
    }

 

注意这段打印出来的宽高,后来设置Camera拍摄的图片大小配置必须是里面的一组,否则无法获取Camera的回调数据,这个很关键

parameters.setPictureSize(screenWidth, screenHeight); // 设置照片的大小

 

还有cpp文件里的宽高也要这样,否则程序会崩溃,其实这里的宽高我们可以通过比例缩放来处理,就可以任意使用宽高,但是我这里没有写。。。。。。。。。

 

  1.  
    int width = 320;
  2.  
    int height = 240;

 

Camera预览回调

camera.setPreviewCallback(new StreamIt()); // 设置回调的类

 

我们在这个回调里传送需要进行推流的数据,这里通过isPlaying标识符控制了,需要我们点击start按钮才会开始推流,并且这里传输数据的代码是通过开启一个单线程来完成,保证上次操作完成了才会执行下一次

  1.  
    public class StreamIt implements Camera.PreviewCallback {
  2.  
    @Override
  3.  
    public void onPreviewFrame(final byte[] data, Camera camera) {
  4.  
    if(isPlaying){
  5.  
    long endTime = System.currentTimeMillis();
  6.  
    executor.execute(new Runnable() {
  7.  
    @Override
  8.  
    public void run() {
  9.  
    encodeTime = System.currentTimeMillis();
  10.  
    FFmpegHandle.getInstance().onFrameCallback(data);
  11.  
    LogUtils.w("编码第:" + (encodeCount++) + "帧,耗时:" + (System.currentTimeMillis() - encodeTime));
  12.  
    }
  13.  
    });
  14.  
    LogUtils.d("采集第:" + (++count) + "帧,距上一帧间隔时间:"
  15.  
    + (endTime - previewTime) + " " + Thread.currentThread().getName());
  16.  
    previewTime = endTime;
  17.  
    }
  18.  
     
  19.  
    }
  20.  
    }

之前还执行了initVideo函数,初始化了FFmpeg并传输了推流地址

计算编码出的yuv数据的大小

  1.  
    yuv_width = width;
  2.  
    yuv_height = height;
  3.  
    y_length = width * height;
  4.  
    uv_length = width * height / 4;

 

初始化组件和输出编码环境

  1.  
    av_register_all();
  2.  
     
  3.  
    //output initialize
  4.  
    avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_path);
  5.  
    //output encoder initialize
  6.  
    pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
  7.  
    if (!pCodec) {
  8.  
    loge("Can not find encoder!\n");
  9.  
    return -1;
  10.  
    }

 

配置编码环境

  1.  
    pCodecCtx = avcodec_alloc_context3(pCodec);
  2.  
    //编码器的ID号,这里为264编码器,可以根据video_st里的codecID 参数赋值
  3.  
    pCodecCtx->codec_id = pCodec->id;
  4.  
    //像素的格式,也就是说采用什么样的色彩空间来表明一个像素点
  5.  
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
  6.  
    //编码器编码的数据类型
  7.  
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
  8.  
    //编码目标的视频帧大小,以像素为单位
  9.  
    pCodecCtx->width = width;
  10.  
    pCodecCtx->height = height;
  11.  
    pCodecCtx->framerate = (AVRational) {fps, 1};
  12.  
    //帧率的基本单位,我们用分数来表示,
  13.  
    pCodecCtx->time_base = (AVRational) {1, fps};
  14.  
    //目标的码率,即采样的码率;显然,采样码率越大,视频大小越大
  15.  
    pCodecCtx->bit_rate = 400000;
  16.  
    //固定允许的码率误差,数值越大,视频越小
  17.  
    // pCodecCtx->bit_rate_tolerance = 4000000;
  18.  
    pCodecCtx->gop_size = 50;
  19.  
    /* Some formats want stream headers to be separate. */
  20.  
    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
  21.  
    pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
  22.  
     
  23.  
    //H264 codec param
  24.  
    // pCodecCtx->me_range = 16;
  25.  
    //pCodecCtx->max_qdiff = 4;
  26.  
    pCodecCtx->qcompress = 0.6;
  27.  
    //最大和最小量化系数
  28.  
    pCodecCtx->qmin = 10;
  29.  
    pCodecCtx->qmax = 51;
  30.  
    //Optional Param
  31.  
    //两个非B帧之间允许出现多少个B帧数
  32.  
    //设置0表示不使用B帧
  33.  
    //b 帧越多,图片越小
  34.  
    pCodecCtx->max_b_frames = 0;
  35.  
     
  36.  
    if (pCodecCtx->codec_id == AV_CODEC_ID_H264) {
  37.  
    // av_dict_set(¶m, "preset", "slow", 0);
  38.  
    /**
  39.  
    * 这个非常重要,如果不设置延时非常的大
  40.  
    * ultrafast,superfast, veryfast, faster, fast, medium
  41.  
    * slow, slower, veryslow, placebo. 这是x264编码速度的选项
  42.  
    */
  43.  
    av_dict_set(¶m, "preset", "superfast", 0);
  44.  
    av_dict_set(¶m, "tune", "zerolatency", 0);
  45.  
    }

 

打开编码器

  1.  
    if (avcodec_open2(pCodecCtx, pCodec, ¶m) < 0) {
  2.  
    loge("Failed to open encoder!\n");
  3.  
    return -1;
  4.  
    }

 


创建并配置一个视频流

  1.  
    video_st = avformat_new_stream(ofmt_ctx, pCodec);
  2.  
    if (video_st == NULL) {
  3.  
    return -1;
  4.  
    }
  5.  
    video_st->time_base.num = 1;
  6.  
    video_st->time_base.den = fps;
  7.  
    // video_st->codec = pCodecCtx;
  8.  
    video_st->codecpar->codec_tag = 0;
  9.  
    avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);

 

查看输出url是否有效,并根据输出格式写入文件头

  1.  
    if (avio_open(&ofmt_ctx->pb, out_path, AVIO_FLAG_READ_WRITE) < 0) {
  2.  
    loge("Failed to open output file!\n");
  3.  
    return -1;
  4.  
    }
  5.  
     
  6.  
    //Write File Header
  7.  
    avformat_write_header(ofmt_ctx, NULL);

 

接下来就是处理Camera传送过来的数据

转换数据格式

    jbyte *in = env->GetByteArrayElements(buffer_, NULL);

 

根据编码器获取缓存图片大小,并创建缓存图片空间

  1.  
    int picture_size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width,
  2.  
    pCodecCtx->height, 1);
  3.  
    uint8_t *buffers = (uint8_t *) av_malloc(picture_size);

 

将之前创建的缓存图片空间赋予AVFrame

  1.  
    pFrameYUV = av_frame_alloc();
  2.  
    //将buffers的地址赋给AVFrame中的图像数据,根据像素格式判断有几个数据指针
  3.  
    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, buffers, pCodecCtx->pix_fmt,
  4.  
    pCodecCtx->width, pCodecCtx->height, 1);

 

转换AVFrame格式,卓摄像头数据为NV21格式,此处将其转换为YUV420P格式

  1.  
    memcpy(pFrameYUV->data[0], in, y_length); //Y
  2.  
    pFrameYUV->pts = count;
  3.  
    for (int i = 0; i < uv_length; i++) {
  4.  
    //将v数据存到第三个平面
  5.  
    *(pFrameYUV->data[2] + i) = *(in + y_length + i * 2);
  6.  
    //将U数据存到第二个平面
  7.  
    *(pFrameYUV->data[1] + i) = *(in + y_length + i * 2 + 1);
  8.  
    }
  9.  
     
  10.  
    pFrameYUV->format = AV_PIX_FMT_YUV420P;
  11.  
    pFrameYUV->width = yuv_width;
  12.  
    pFrameYUV->height = yuv_height;

 

编码AVFrame数据

avcodec_send_frame(pCodecCtx, pFrameYUV);

获取编码后得到的数据

avcodec_receive_packet(pCodecCtx, &enc_pkt);

释放AVFrame

av_frame_free(&pFrameYUV);

对编码后的数据进行配置,设置播放时间等

  1.  
    enc_pkt.stream_index = video_st->index;
  2.  
    AVRational time_base = ofmt_ctx->streams[0]->time_base;//{ 1, 1000 };
  3.  
    enc_pkt.pts = count * (video_st->time_base.den) / ((video_st->time_base.num) * fps);
  4.  
    enc_pkt.dts = enc_pkt.pts;
  5.  
    enc_pkt.duration = (video_st->time_base.den) / ((video_st->time_base.num) * fps);
  6.  
    __android_log_print(ANDROID_LOG_WARN, "eric",
  7.  
    "index:%d,pts:%lld,dts:%lld,duration:%lld,time_base:%d,%d",
  8.  
    count,
  9.  
    (long long) enc_pkt.pts,
  10.  
    (long long) enc_pkt.dts,
  11.  
    (long long) enc_pkt.duration,
  12.  
    time_base.num, time_base.den);
  13.  
    enc_pkt.pos = -1;

 

进行推流

av_interleaved_write_frame(ofmt_ctx, &enc_pkt);

释放Camera传输过来的数据

env->ReleaseByteArrayElements(buffer_, in, 0);

最后释放所有资源

  1.  
    if (video_st)
  2.  
    avcodec_close(video_st->codec);
  3.  
    if (ofmt_ctx) {
  4.  
    avio_close(ofmt_ctx->pb);
  5.  
    avformat_free_context(ofmt_ctx);
  6.  
    ofmt_ctx = NULL;
  7.  
    }

 

4.VLC的使用

在进行推流时,输入推流地址,观看推流数据,效果如下

分类:

技术点:

相关文章: