源码地址
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的使用

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

Android 音视频深入 十六 FFmpeg 推流手机摄像头,实现直播 (附源码下载)的更多相关文章

  1. Android 音视频深入 十九 使用ijkplayer做个视频播放器(附源码下载)

    项目地址https://github.com/979451341/Myijkplayer 前段时候我觉得FFmpeg做个视频播放器好难,虽然播放上没问题,但暂停还有通过拖动进度条来设置播放进度,这些都 ...

  2. Android 音视频深入 一 AudioRecord录音生成pcm转换为wav(附源码下载)

    本篇项目地址,名字是AudioRecord录音(能暂停,将pch转换为wav),求starhttps://github.com/979451341/Audio-and-video-learning-m ...

  3. Android 音视频深入 十五 FFmpeg 推流mp4文件(附源码下载)

    源码地址https://github.com/979451341/Rtmp 1.配置RTMP服务器 这个我不多说贴两个博客分别是在mac和windows环境上的,大家跟着弄 MAC搭建RTMP服务器h ...

  4. Android 音视频深入 十四 FFmpeg与OpenSL ES 播放mp3音乐,能暂停(附源码下载)

    项目地址https://github.com/979451341/FFmpegOpenslES 这次说的是FFmpeg解码mp3,数据给OpenSL ES播放,并且能够暂停. 1.创建引擎 slCre ...

  5. Android 音视频深入 十二 FFmpeg视频替换声音(附源码下载)

    项目地址,求starhttps://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpeg%E7%BB%99%E8%A7%86%E ...

  6. Android 音视频深入 十八 FFmpeg播放视频,有声音(附源码下载)

    项目地址https://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpegv%E6%92%AD%E6%94%BE%E8%A7% ...

  7. 第十六节、特征描述符BRIEF(附源码)

    我们已经知道SIFT算法采用128维的特征描述子,由于描述子用的是浮点数,所以它将会占用512字节的空间.类似的SUFR算法,一般采用64维的描述子,它将占用256字节的空间.如果一幅图像中有1000 ...

  8. Android中Canvas绘图基础详解(附源码下载) (转)

    Android中Canvas绘图基础详解(附源码下载) 原文链接  http://blog.csdn.net/iispring/article/details/49770651   AndroidCa ...

  9. Android网络:HTTP之利用HttpURLConnection访问网页、获取网络图片实例 (附源码)

    http://blog.csdn.net/yanzi1225627/article/details/22222735 如前文所示的TCP局域网传送东西,除了对传输层的TCP/UDP支持良好外,Andr ...

随机推荐

  1. prometheus监控

    一. 部署prometheus 1.1 下载 wget https://github.com/prometheus/prometheus/releases/download/v2.7.1/promet ...

  2. window bat 切换目录并执行php文件

    新建一个 test.bat文件,输入一下命令并保存 cmd /k "cd /d D:\PHPWAMP_IN2\phpwamp\server\Nginx-PHPWNMP\htdocs\test ...

  3. Parallels Desktop 14.1.3中文版win系统安装教程

    parallels desktop 14 中文版(pd虚拟机)是mac上最强大也是最好用的虚拟机软件,本站第一时间为大家带来这款parallels desktop 14 破解版,最新版本的parall ...

  4. 踩坑之路---JWT验证

    使用JWT验证客户的携带的token 客户端在请求接口时,需要在request的head中携带一个token令牌 服务器拿到这个token解析获取用户资源,这里的资源是非重要的用户信息 目前我的理解, ...

  5. Spring history、design philosophy (Spring的历史及设计理念)

    一,Spring的发展史 1,Spring1.x 时代 在Spring1.x时代,都是通过xml文件配置bean,随着项目的不断扩大,需要将xml配置分放到不同的配置文件中,需要频繁的在java类和x ...

  6. 【BZOJ5194】Snow Boots

    [原题题面]传送门 [简化题意] 给定一个长度为n的序列. 有m次询问,每次询问给定两个数si,di.你一开始站在0,每次你可以走不超过di,但你到达的位置的数不能超过si.问能否走到n+1. n,m ...

  7. vue引入JQ的方法

    在vue中引入jq 用vue-cli脚手架工具构建项目成功后 当需要引入JQ,可用以下方法: 1.首先在package.json里的 dependencies加入"jquery" ...

  8. GhostCore核心使用完全指南 - 传送门

    Ghostcore,小坏制作,QQ 532073265,切记不要使用破解版本,后果自负 更新日志 数据表集合 了解模板机制 基本设置 自动备份数据库 自定义字符 扩展的GM命令 NPC脚本(包括幻化. ...

  9. shell 的多进程

    例子 #!/bin/bash temp_fifo_file=$$.info #以当前进程号,为临时管道取名 mkfifo $temp_fifo_file #创建临时管道 exec <>$t ...

  10. chordDiagramFromMatrix()函数与circos.link()函数结合绘制箭头线

    chordDiagramFromMatrix(matp2,annotationTrack="grid",                       grid.col = c(re ...