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的配置有的需要注意
-
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的使用
在进行推流时,输入推流地址,观看推流数据,效果如下