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中获取网页表单中的数据
  2. Android数据缓存
  3. Android解析服务器端发来的xml数据
  4. Android android下的数据持久化和读取数据,保存数据到手机及SD卡
  5. Android连接Mysql数据库教程以及增删改查
  6. Android本地数据存储之SQLite
  7. 使用AudioTrack播放PCM音频数据(android)

随机推荐

  1. Android(安卓)Studio 构建变体(Build Vari
  2. 无法对jar进行签名,Android(安卓)jarsign
  3. 导入的android项目报错,进入一个类,显示不
  4. Android环境搭建(三)————继续动手
  5. android有序广播
  6. 浅谈Android(安卓)onTouchEvent 与 onInt
  7. Android中Bitmap用法实例分析
  8. ANDROID SDK配置步骤
  9. 《Android开发艺术探索》读书笔记——Han
  10. [置顶] Bottom Sheet