9 FFmpeg4Android:Android摄像头直播

9.1 摄像头图像预览

java层Android摄像头摄像代码

package com.ffmpeg.pusher;import android.graphics.ImageFormat;import android.hardware.Camera;import android.util.Log;import android.view.SurfaceHolder;import android.hardware.Camera.CameraInfo;import com.ffmpeg.listener.LiveStateChangeListener;import com.ffmpeg.parame.VideoParam;import com.ffmpeg.jni.PushNative;import java.io.IOException;/** * Created by lizhiping03 on 2018/1/14. */public class VideoPusher extends Pusher implements SurfaceHolder.Callback, Camera.PreviewCallback {    private static final String TAG = "VideoPusher";    private SurfaceHolder surfaceHolder;    private Camera mCamera;    private VideoParam videoParams;    private byte[] buffers;    private boolean isPushing = false;    private PushNative pushNative;    public VideoPusher(SurfaceHolder surfaceHolder, VideoParam videoParams, PushNative pushNative) {        this.surfaceHolder = surfaceHolder;        this.videoParams = videoParams;        this.pushNative = pushNative;        surfaceHolder.addCallback(this);    }    @Override    public void startPush(LiveStateChangeListener listener) {        // 设置视频参数        pushNative.setVideoOptions(videoParams.getWidth(),                videoParams.getHeight(), videoParams.getBitrate(), videoParams.getFps());        pushNative.setLiveStateChangeListener(listener);        isPushing = true;    }    @Override    public void stopPush() {        isPushing = false;    }    @Override    public void surfaceCreated(SurfaceHolder holder) {        startPreview();    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        stopPush();        release();    }    public void release() {        stopPreview();        pushNative.release();    }    /**     * 切换摄像头     */    public void switchCamera() {        if (videoParams.getCameraId() == Camera.CameraInfo.CAMERA_FACING_BACK) {            videoParams.setCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT);        } else {            videoParams.setCameraId(CameraInfo.CAMERA_FACING_BACK);        }        // 重新预览        stopPreview();        startPreview();    }    /**     * 开始预览     */    private void startPreview() {        try {            // SurfaceView初始化完成,开始相机预览            mCamera = Camera.open(videoParams.getCameraId());            Camera.Parameters parameters = mCamera.getParameters();            // 设置相机参数            parameters.setPreviewFormat(ImageFormat.NV21); // YUYV 预览图像的像素格式            parameters.setPreviewSize(videoParams.getWidth(), videoParams.getHeight()); // 预览画面宽高            mCamera.setParameters(parameters);            mCamera.setPreviewDisplay(surfaceHolder);            mCamera.setDisplayOrientation(90);            // 获取预览图像数据            buffers = new byte[videoParams.getWidth() * videoParams.getHeight() * 4];            mCamera.addCallbackBuffer(buffers);            mCamera.setPreviewCallbackWithBuffer(this);            mCamera.startPreview();        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 停止预览     */    private void stopPreview() {        if (mCamera != null) {            mCamera.stopPreview();            mCamera.release();            mCamera = null;        }    }    @Override    public void onPreviewFrame(byte[] data, Camera camera) {        if (mCamera != null) {            mCamera.addCallbackBuffer(buffers);        }        if (isPushing) {            // 回调函数中获取图像数据,然后给Native代码编码            Log.e(TAG, "push frame len:" + data.length);            pushNative.fireVideo(data); // data : nv21(yuyv)数据        }    }}

其中private PushNative pushNative是jni的接口:

package com.ffmpeg.jni;import android.media.AudioFormat;import android.media.AudioManager;import android.media.AudioTrack;import android.util.Log;import android.view.Surface;/** * 视频播放的控制器 */public abstract class PlayNative {    /**     * 原生图像渲染     * @param input     * @param surface     */    public native void render(String input, Surface surface);    /**     * 音频播放     * @param input     * @param output     */    public native void sound(String input, String output);    /**     * 打开视频文件并开始解码     * @param file_url     */    public native void decodeVideo(String file_url, int width, int height);    public abstract void frameCallback(byte[] rgbdata, int width, int heigth);    /**     * 创建一个AudioTrac对象,用于播放     *     * @param nb_channels     * @return     */    public AudioTrack createAudioTrack(int sampleRateInHz, int nb_channels) {        // 固定格式的音频码流        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;        Log.i("jason", "nb_channels:" + nb_channels);        // 声道布局        int channelConfig;        if (nb_channels == 1) {            channelConfig = AudioFormat.CHANNEL_OUT_MONO;        } else if (nb_channels == 2) {            channelConfig = AudioFormat.CHANNEL_OUT_STEREO;        } else {            channelConfig = AudioFormat.CHANNEL_OUT_STEREO;        }        int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);        AudioTrack audioTrack = new AudioTrack(                AudioManager.STREAM_MUSIC,                sampleRateInHz, channelConfig,                audioFormat,                bufferSizeInBytes, AudioTrack.MODE_STREAM);        // 播放        // audioTrack.play();        // 写入PCM        // audioTrack.write(audioData, offsetInBytes, sizeInBytes);        return audioTrack;    }    static {        System.loadLibrary("playutils");    }}

9.2 推流

c层推流代码:

//// Created by lizhiping03 on 2018/1/15.//#include "com_ffmpeg_jni_PushNative.h"#include #include #include #include #include #include "queue.h"#include "x264.h"#include "rtmp.h"#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO, "walker", FORMAT, ##__VA_ARGS__)#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR, "walker", FORMAT, ##__VA_ARGS__)#ifndef TRUE#define TRUE    1#define FALSE   0#endif#define CONNECT_FAILED  101#define INIT_FAILED     102// x264编码输入图像YUV420Px264_picture_t pic_in;x264_picture_t pic_out;// YUV个数int y_len;int u_v_len;// x264编码处理器x264_t * video_encode_handle;unsigned int start_time;// 线程处理pthread_mutex_t mutex;pthread_cond_t cond;// rtmp流媒体地址char * rtmp_path;// 是否直播int is_pushing = FALSE;jobject g_jobj_push_native; // Global refjclass jcls_push_native;jmethodID jmid_throw_native_error;JavaVM * g_jvm;// 获取JavaVMjint JNI_OnLoad(JavaVM * vm, void * reserved){    g_jvm = vm;    return JNI_VERSION_1_4;}/*** 向Java层发送错误信息*/void throwNativeError(JNIEnv * env, int code){    (*env)->CallVoidMethod(env, g_jobj_push_native, jmid_throw_native_error, code);}/*** 从队列中不断拉取RTMPPacket发送给流媒体服务器)*/void *push_thread(void * arg){    JNIEnv* env; // 获取当前线程JNIEnv    (*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL);    // 建立RTMP连接    RTMP *rtmp = RTMP_Alloc();    if (!rtmp) {        LOGE("rtmp初始化失败");        goto end;    }    RTMP_Init(rtmp);    rtmp->Link.timeout = 5; // 连接超时的时间    // 设置流媒体地址    RTMP_SetupURL(rtmp, rtmp_path);    // 发布rtmp数据流    RTMP_EnableWrite(rtmp);    // 建立连接    if (!RTMP_Connect(rtmp, NULL)) {        LOGE("%s", "RTMP 连接失败");        throwNativeError(env, CONNECT_FAILED);        goto end;    }    // 计时    start_time = RTMP_GetTime();    if (!RTMP_ConnectStream(rtmp, 0)) { // 连接流        LOGE("%s", "RTMP ConnectStream failed");        throwNativeError(env, CONNECT_FAILED);        goto end;    }    is_pushing = TRUE;    while (is_pushing) {        // 发送        pthread_mutex_lock(&mutex);        pthread_cond_wait(&cond, &mutex);        // 取出队列中的RTMPPacket        RTMPPacket *packet = queue_get_first();        if (packet) {            queue_delete_first(); // 移除            packet->m_nInfoField2 = rtmp->m_stream_id; // RTMP协议,stream_id数据            int i = RTMP_SendPacket(rtmp, packet, TRUE); // TRUE放入librtmp队列中,并不是立即发送            if (!i) {                LOGE("RTMP 断开");                RTMPPacket_Free(packet);                pthread_mutex_unlock(&mutex);                goto end;            } else {                LOGI("%s", "rtmp send packet");            }            RTMPPacket_Free(packet);        }        pthread_mutex_unlock(&mutex);    }end:    LOGI("%s", "释放资源");    free(rtmp_path);    RTMP_Close(rtmp);    RTMP_Free(rtmp);    (*g_jvm)->DetachCurrentThread(g_jvm);    return 0;}/** Class:     com_ffmpeg_jni_PushNative* Method:    setVideoOptions* Signature: (IIII)V*/JNIEXPORT void JNICALL Java_com_ffmpeg_jni_PushNative_setVideoOptions(JNIEnv *env, jobject jobj, jint width, jint height, jint bitrate, jint fps){    x264_param_t param;    // x264_param_default_preset 设置    x264_param_default_preset(¶m, "ultrafast", "zerolatency");    // 编码输入的像素格式YUV420P    param.i_csp = X264_CSP_I420; // 仅支持i420    param.i_width = width;    param.i_height = height;    y_len = width * height;    u_v_len = y_len / 4;    // 参数i_rc_method表示码率控制,CQP(恒定质量),CRF(恒定码率),ABR(平均码率)    // 恒定码率,会尽量控制在固定码率    param.rc.i_rc_method = X264_RC_CRF;    param.rc.i_bitrate = bitrate / 1000; // 码率(比特率,单位Kbps)    param.rc.i_vbv_max_bitrate = bitrate / 1000 * 1.2; // 瞬时最大码率    // 码率控制不通过timebase和timestamp,而是fps    param.b_vfr_input = 0;    param.i_fps_num = fps; // 帧率分子    param.i_fps_den = 1; // 帧率分母    param.i_timebase_den = param.i_fps_num;    param.i_timebase_num = param.i_fps_den;    param.i_threads = 1; // 并行编码线程数量,0默认为多线程    // 是否把SPS和PPS放入每一个关键帧    // SPS Sequence Parameter Set 序列参数集,PPS Picture Parameter Set 图像参数集    // 为了提高图像的纠错能力    param.b_repeat_headers = 1;    // 设置Level级别    param.i_level_idc = 51;    // 设置Profile档次    // baseline级别,没有B帧    x264_param_apply_profile(¶m, "baseline");    // x264_picture_t(输入图像)初始化    x264_picture_alloc(&pic_in, param.i_csp, param.i_width, param.i_height);    pic_in.i_pts = 0;    // 打开编码器    video_encode_handle = x264_encoder_open(¶m);    if (video_encode_handle){        LOGI("打开视频编码器成功");    } else {        throwNativeError(env, INIT_FAILED);    }}/** Class:     com_ffmpeg_jni_PushNative* Method:    startPush* Signature: (Ljava/lang/String;)V*/JNIEXPORT void JNICALL Java_com_ffmpeg_jni_PushNative_startPush(JNIEnv *env, jobject jobj, jstring url_jstr){    // jobj(PushNative对象)    g_jobj_push_native = (*env)->NewGlobalRef(env, jobj);    jclass jcls_push_native_tmp = (*env)->GetObjectClass(env, jobj);    jcls_push_native = (*env)->NewGlobalRef(env, jcls_push_native_tmp);    if (jcls_push_native_tmp == NULL) {        LOGI("%s", "NULL");    } else {        LOGI("%s", "not NULL");    }    // PushNative.throwNativeError    jmid_throw_native_error = (*env)->GetMethodID(env, jcls_push_native_tmp, "throwNativeError", "(I)V");    // 初始化的操作    const char * url_cstr = (*env)->GetStringUTFChars(env, url_jstr, NULL);    // 复制url_cstr内容到rtmp_path    rtmp_path = malloc(strlen(url_cstr) + 1);    memset(rtmp_path, 0, strlen(url_cstr) + 1);    memcpy(rtmp_path, url_cstr, strlen(url_cstr));    // 初始化互斥锁与条件变量    pthread_mutex_init(&mutex, NULL);    pthread_cond_init(&cond, NULL);    // 创建队列    create_queue();    // 启动消费者线程(从队列中不断拉取RTMPPacket发送给流媒体服务器)    pthread_t push_thread_id;    pthread_create(&push_thread_id, NULL, push_thread, NULL);    (*env)->ReleaseStringUTFChars(env, url_jstr, url_cstr);}/*** 加入RTMPPacket队列,等待发送线程发送*/void add_rtmp_packet(RTMPPacket * packet){    pthread_mutex_lock(&mutex);    if (is_pushing) {        queue_append_last(packet);    }    pthread_cond_signal(&cond);    pthread_mutex_unlock(&mutex);}/*** 发送h264 SPS与PPS参数集*/void add_264_sequence_header(unsigned char* pps, int pps_len, unsigned char* sps, int sps_len){    int body_size = 16 + sps_len + pps_len; // 按照H264标准配置SPS和PPS,共使用了16字节    RTMPPacket * packet = malloc(sizeof(RTMPPacket));    //RTMPPacket初始化    RTMPPacket_Alloc(packet, body_size);    RTMPPacket_Reset(packet);    unsigned char * body = packet->m_body;    int i = 0;    // 二进制表示:00010111    body[i++] = 0x17; // VideoHeaderTag:FrameType(1=key frame)+CodecID(7=AVC)    body[i++] = 0x00; // AVCPacketType = 0表示设置AVCDecoderConfigurationRecord    // composition time 0x000000 24bit ?    body[i++] = 0x00;    body[i++] = 0x00;    body[i++] = 0x00;    /*AVCDecoderConfigurationRecord*/    body[i++] = 0x01; // configurationVersion,版本为1    body[i++] = sps[1]; // AVCProfileIndication    body[i++] = sps[2]; // profile_compatibility    body[i++] = sps[3]; // AVCLevelIndication    // ?    body[i++] = 0xFF; // lengthSizeMinusOne,H264 视频中 NALU的长度,计算方法是 1 + (lengthSizeMinusOne & 3),实际测试时发现总为FF,计算结果为4.    /*sps*/    body[i++] = 0xE1; // numOfSequenceParameterSets:SPS的个数,计算方法是 numOfSequenceParameterSets & 0x1F,实际测试时发现总为E1,计算结果为1.    body[i++] = (sps_len >> 8) & 0xff; // sequenceParameterSetLength:SPS的长度    body[i++] = sps_len & 0xff; // sequenceParameterSetNALUnits    memcpy(&body[i], sps, sps_len);    i += sps_len;    /*pps*/    body[i++] = 0x01; // numOfPictureParameterSets:PPS 的个数,计算方法是 numOfPictureParameterSets & 0x1F,实际测试时发现总为E1,计算结果为1.    body[i++] = (pps_len >> 8) & 0xff; // pictureParameterSetLength:PPS的长度    body[i++] = (pps_len)& 0xff; // PPS    memcpy(&body[i], pps, pps_len);    i += pps_len;    // Message Type,RTMP_PACKET_TYPE_VIDEO:0x09    packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;    // Payload Length    packet->m_nBodySize = body_size;    // Time Stamp:4字节    // 记录了每一个tag相对于第一个tag(File Header)的相对时间。    // 以毫秒为单位。而File Header的time stamp永远为0。    packet->m_nTimeStamp = 0;    packet->m_hasAbsTimestamp = 0;    packet->m_nChannel = 0x04; // Channel ID,Audio和Vidio通道    packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; // ?    // 将RTMPPacket加入队列    add_rtmp_packet(packet);}/*** 发送h264帧信息*/void add_264_body(unsigned char * buf, int len){    // 去掉起始码(界定符)    if (buf[2] == 0x00) {  //00 00 00 01        buf += 4;        len -= 4;    }    else if (buf[2] == 0x01) { // 00 00 01        buf += 3;        len -= 3;    }    int body_size = len + 9;    RTMPPacket * packet = malloc(sizeof(RTMPPacket));    RTMPPacket_Alloc(packet, body_size);    unsigned char * body = packet->m_body;    // 当NAL头信息中,type(5位)等于5,说明这是关键帧NAL单元    // buf[0] NAL Header与运算,获取type,根据type判断关键帧和普通帧    // 00000101 & 00011111(0x1f) = 00000101    int type = buf[0] & 0x1f;    // Inter Frame 帧间压缩    body[0] = 0x27; // VideoHeaderTag:FrameType(2=Inter Frame)+CodecID(7=AVC)    // IDR I帧图像    if (type == NAL_SLICE_IDR) {        body[0] = 0x17; // VideoHeaderTag:FrameType(1=key frame)+CodecID(7=AVC)    }    // AVCPacketType = 1    body[1] = 0x01; /*nal unit,NALUs(AVCPacketType == 1)*/    body[2] = 0x00; // composition time 0x000000 24bit    body[3] = 0x00;    body[4] = 0x00;    // 写入NALU信息,右移8位,一个字节的读取?    body[5] = (len >> 24) & 0xff;    body[6] = (len >> 16) & 0xff;    body[7] = (len >> 8) & 0xff;    body[8] = (len)& 0xff;    /*copy data*/    memcpy(&body[9], buf, len);    packet->m_hasAbsTimestamp = 0;    packet->m_nBodySize = body_size;    packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; // 当前packet的类型:Video    packet->m_nChannel = 0x04;    packet->m_headerType = RTMP_PACKET_SIZE_LARGE;    // packet->m_nTimeStamp = -1;    packet->m_nTimeStamp = RTMP_GetTime() - start_time; // 记录了每一个tag相对于第一个tag(File Header)的相对时间    add_rtmp_packet(packet);}/** Class:     com_ffmpeg_jni_PushNative* Method:    fireVideo* Signature: ([B)V*/JNIEXPORT void JNICALL Java_com_ffmpeg_jni_PushNative_fireVideo    (JNIEnv *env, jobject jobj, jbyteArray buffer){    // 视频数据转为YUV420P    // NV21->YUV420P    jbyte* nv21_buffer = (*env)->GetByteArrayElements(env, buffer, NULL);    jbyte* u = pic_in.img.plane[1];    jbyte* v = pic_in.img.plane[2];    // nv21 4:2:0 Formats, 12 Bits per Pixel    // nv21与yuv420p,y个数一致,uv位置对调    // nv21转yuv420p y = w*h,u、v = w*h/4    // nv21:    yyyyyyyy vu vu    // yuv420p: yyyyyyyy uu vv    memcpy(pic_in.img.plane[0], nv21_buffer, y_len);    int i;    for (i = 0; i < u_v_len; i++) {        *(u + i) = *(nv21_buffer + y_len + i * 2 + 1);        *(v + i) = *(nv21_buffer + y_len + i * 2);    }    // h264编码得到NALU数组    x264_nal_t * nal = NULL; // NAL    int n_nal = -1; // NALU的个数    // 进行h264编码    if (x264_encoder_encode(video_encode_handle, &nal, &n_nal, &pic_in, &pic_out) < 0) {        LOGE("%s", "编码失败");        return;    }    // 使用rtmp协议将h264编码的视频数据发送给流媒体服务器    // 帧分为关键帧和普通帧,为了提高画面的纠错率,关键帧应包含SPS和PPS数据    int sps_len, pps_len;    unsigned char sps[100];    unsigned char pps[100];    memset(sps, 0, 100);    memset(pps, 0, 100);    pic_in.i_pts++; // 顺序累加    // 遍历NALU数组,根据NALU的类型判断    for (i = 0; i < n_nal; i++) {        if (nal[i].i_type == NAL_SPS) {            // 复制SPS数据            sps_len = nal[i].i_payload - 4;            memcpy(sps, nal[i].p_payload + 4, sps_len); // 不复制四字节起始码        } else if (nal[i].i_type == NAL_PPS) {            // 复制PPS数据            pps_len = nal[i].i_payload - 4;            memcpy(pps, nal[i].p_payload + 4, pps_len); // 不复制四字节起始码            // 发送序列信息            // h264关键帧会包含SPS和PPS数据            add_264_sequence_header(pps, pps_len, sps, sps_len);        } else {            // 发送帧信息            add_264_body(nal[i].p_payload, nal[i].i_payload);        }    }    (*env)->ReleaseByteArrayElements(env, buffer, nv21_buffer, NULL);}/** Class:     com_ffmpeg_jni_PushNative* Method:    stopPush* Signature: ()V*/JNIEXPORT void JNICALL Java_com_ffmpeg_jni_PushNative_stopPush(JNIEnv *env, jobject jobj){    is_pushing = FALSE;}/** Class:     com_ffmpeg_jni_PushNative* Method:    release* Signature: ()V*/JNIEXPORT void JNICALL Java_com_ffmpeg_jni_PushNative_release(JNIEnv *env, jobject jobj){    (*env)->DeleteGlobalRef(env, jcls_push_native);    (*env)->DeleteGlobalRef(env, g_jobj_push_native);    (*env)->DeleteGlobalRef(env, jmid_throw_native_error);}

更多相关文章

  1. Android(安卓)Handler机制之Message的发送与取出
  2. 界面编程之基本界面组件(7)ImageView(图像视图)
  3. Android使用ksoap2调用C#中的webservice实现图像上传
  4. [原]如何在Android用FFmpeg+SDL2.0解码声音
  5. Java的消息机制
  6. [android—runloop] vs [ios—runloop]
  7. Android(安卓)Priority Job Queue:一个专门为Android轻松调度任务
  8. Android应用程序消息处理机制(Looper、Handler)分析(1)
  9. Android核心分析 之十一-------Android(安卓)GWES之消息系统

随机推荐

  1. android Google推荐的容器SparseArrayCom
  2. Android编程获取网络连接状态(3G/Wifi)及
  3. Android个人手机通讯录开发详解
  4. Android(安卓)Launcher3浅析(一)
  5. ionic3文件目录介绍
  6. Android调用系统相机和相册,解决图片方向
  7. [Android]使用化名(alias)功能防止相同资
  8. Android(安卓)中屏幕点击事件的实现
  9. android自定义Spinner下拉菜单样式并获得
  10. Android 在列表List中显示半透明小窗体效