Android 音視頻深入 十六 FFmpeg 推流手機網路攝影機,實現直播 (附源碼下載),androidffmpeg
源碼地址
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的使用
在進行推流時,輸入推流地址,觀看推流資料,效果如下