源码地址
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 的配置有的需要注意
- 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 的使用
在进行推流时, 输入推流地址, 观看推流数据, 效果如下
来源: http://blog.csdn.net/z979451341/article/details/79398647