美文网首页
Android Media 13 --- camerax+x26

Android Media 13 --- camerax+x26

作者: 沪漂意哥哥 | 来源:发表于2022-04-27 07:15 被阅读0次

一.VideoChanel.java

public class VideoChanel implements Preview.OnPreviewOutputUpdateListener, ImageAnalysis.Analyzer {
   private TextureView textureView;
   private LivePusher livePusher;
   int width = 480;
   int height = 640;
   private HandlerThread handlerThread;
   private CameraX.LensFacing currentFacing = CameraX.LensFacing.BACK;
   private boolean isLiving;
   private byte[] y;
   private byte[] u;
   private byte[] v;
   private byte[] nv21;
   byte[] nv21_rotated;
   public VideoChanel(LifecycleOwner lifecycleOwner, TextureView textureView, LivePusher livePusher) {
       this.textureView = textureView;
       this.livePusher = livePusher;
       handlerThread = new HandlerThread("Analyze-thread");
       handlerThread.start();
       CameraX.bindToLifecycle(lifecycleOwner, getPreView(), getAnalysis());

   }

   private Preview getPreView() {
       PreviewConfig previewConfig = new PreviewConfig.Builder()
               .setTargetResolution(new Size(width, height))
               .setLensFacing(currentFacing).build();
       Preview preview = new Preview(previewConfig);
       preview.setOnPreviewOutputUpdateListener(this);
       return preview;
   }

   private ImageAnalysis getAnalysis() {
       ImageAnalysisConfig imageAnalysisConfig = new ImageAnalysisConfig.Builder()
               .setCallbackHandler(new Handler(handlerThread.getLooper()))
               .setLensFacing(currentFacing)
               .setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
               .setTargetResolution(new Size(width, height))
               .build();
       ImageAnalysis imageAnalysis = new ImageAnalysis(imageAnalysisConfig);
       imageAnalysis.setAnalyzer(this);
       return imageAnalysis;
   }

   @Override
   public void onUpdated(Preview.PreviewOutput output) {
       SurfaceTexture surfaceTexture = output.getSurfaceTexture();
       if (textureView.getSurfaceTexture() != surfaceTexture) {
           if (textureView.isAvailable()) {
           // 当切换摄像头时,会报错
               ViewGroup parent = (ViewGroup) textureView.getParent();
               parent.removeView(textureView);
               parent.addView(textureView, 0);
               parent.requestLayout();
           }
           textureView.setSurfaceTexture(surfaceTexture);
       }
   }

   public void startLive() {
       isLiving = true;
   }
   public void stopLive() {
       isLiving = false;
   }
   private ReentrantLock lock = new ReentrantLock();
   @Override
   public void analyze(ImageProxy image, int rotationDegrees) {
       if (!isLiving) {
           return;
       }
       lock.lock();
       ImageProxy.PlaneProxy[] planes =  image.getPlanes();
       if (y == null) {
           y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];
           u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];
           v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];
           livePusher.native_setVideoEncInfo(image.getHeight(), image.getWidth(), 10, 640_000);
       }

       if (image.getPlanes()[0].getBuffer().remaining() == y.length) {
           planes[0].getBuffer().get(y);
           planes[1].getBuffer().get(u);
           planes[2].getBuffer().get(v);
           int stride = planes[0].getRowStride();
           Size size = new Size(image.getWidth(), image.getHeight());
           int width = size.getHeight();
           int heigth = image.getWidth();
           if (nv21 == null) {
               nv21 = new byte[heigth * width * 3 / 2];
               nv21_rotated = new byte[heigth * width * 3 / 2];
           }
           ImageUtil.yuvToNv21(y, u, v, nv21, heigth, width);
           ImageUtil.nv21_rotate_to_90(nv21, nv21_rotated, heigth, width);
           this.livePusher.native_pushVideo(nv21_rotated);
       }
       lock.unlock();
   }

   public void switchCamera() {

   }
}

二.AudioChannel.java

public class AudioChannel {
   private LivePusher livePusher;
   private int sampleRate;
   private int channelConfig;
   private int minBufferSize;
   private byte[] buffer;
   private Handler handler;
   private HandlerThread handlerThread;
   private AudioRecord audioRecord;
   public AudioChannel(int sampleRate, int channels, LivePusher livePusher) {
       this.livePusher = livePusher;
       this.sampleRate = sampleRate;
       this.channelConfig = channels == 2 ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
       this.minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig,AudioFormat.ENCODING_PCM_16BIT);
       int inputByteNum =  livePusher.initAudioEnc(sampleRate, channels);
       this.buffer = new byte[inputByteNum];
       this.minBufferSize = inputByteNum > minBufferSize ? inputByteNum : minBufferSize;
       handlerThread = new HandlerThread("Audio-Record");
       handlerThread.start();
       handler = new Handler(handlerThread.getLooper());
   }

   public void start() {
       handler.post(new Runnable() {
           @Override
           public void run() {
               audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                       sampleRate, channelConfig,
                       AudioFormat.ENCODING_PCM_16BIT, minBufferSize);
               audioRecord.startRecording();
               while (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
//                    len实际长度len 打印下这个值  录音不成功
                   int len = audioRecord.read(buffer, 0, buffer.length);
                   if (len > 0) {
                       livePusher.sendAudio(buffer, len/2);
                   }
               }
           }
       });
   }
}

三.LivePusher.java

public class LivePusher {
   static {
       System.loadLibrary("native-lib");
   }
   public LivePusher() {
       native_init();
   }

   public void startLive(String path) {
       native_start(path);
   }

   public void sendAudio(byte[] buffer, int len) {
       nativeSendAudio(buffer, len);
   }
   public native void native_init();
   public native void native_start(String path);
   public native void native_setVideoEncInfo(int width, int height, int fps, int bitrate);
   public native int initAudioEnc(int sampleRate, int channels);
   public native void native_pushVideo(byte[] data);
   private native void nativeSendAudio(byte[] buffer, int len);
   public native void native_stop();
   public native void native_release();

}

四.native-lib.cpp

#include <jni.h>
#include <string>
#include <android/log.h>
#include <malloc.h>
extern "C"{
#include "librtmp/rtmp.h"
}
#include "safe_queue.h"
#include "VideoChannel.h"
#include "AudioChannel.h"
#include "maniulog.h"


VideoChannel *videoChannel = 0;
AudioChannel *audioChannel = 0;
int isStart = 0;
//记录子线程的对象
pthread_t pid;
//推流标志位
int readyPushing = 0;
//阻塞式队列
SafeQueue<RTMPPacket *> packets;

uint32_t start_time;
RTMP *rtmp = 0;
//虚拟机的引用
JavaVM *javaVM = 0;
void callBack(RTMPPacket *packet) {
   if (packet) {
       if (packets.size() > 50) {
           packets.clear();
       }
       if (packet->m_packetType == RTMP_PACKET_TYPE_AUDIO ) {
           LOGE("发送音频");
       } else {
           LOGE("发送视频");
       }
       packet->m_nTimeStamp = RTMP_GetTime() - start_time;
       packets.push(packet);
   }
}
//RTMPPacket释放
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
   javaVM = vm;
   LOGE("保存虚拟机的引用");
   return JNI_VERSION_1_4;
}
void releasePackets(RTMPPacket *&packet) {
   if (packet) {
       RTMPPacket_Free(packet);
       delete packet;
       packet = 0;
   }
}
void *start(void *args) {
   char *url = static_cast<char *>(args);
   do {
       rtmp = RTMP_Alloc();
       if (!rtmp) {
           LOGE("rtmp创建失败");
           break;
       }
       RTMP_Init(rtmp);
       //设置超时时间 5s
       rtmp->Link.timeout = 5;
       int ret = RTMP_SetupURL(rtmp, url);
       if (!ret) {
           LOGE("rtmp设置地址失败:%s", url);
           break;
       }
       //开启输出模式
       RTMP_EnableWrite(rtmp);
       ret = RTMP_Connect(rtmp, 0);
       if (!ret) {
           LOGE("rtmp连接地址失败:%s", url);
           break;
       }
       ret = RTMP_ConnectStream(rtmp, 0);

       LOGE("rtmp连接成功----------->:%s", url);
       if (!ret) {
           LOGE("rtmp连接流失败:%s", url);
           break;
       }

       //准备好了 可以开始推流了
       readyPushing = 1;
       //记录一个开始推流的时间
       start_time = RTMP_GetTime();
       packets.setWork(1);
       RTMPPacket *packet = 0;

       RTMPPacket *audioHeader =audioChannel->getAudioConfig();
       callBack(audioHeader);
       //循环从队列取包 然后发送
       while (isStart) {
           packets.pop(packet);
           if (!isStart) {
               break;
           }
           if (!packet) {
               continue;
           }
           // 给rtmp的流id
           packet->m_nInfoField2 = rtmp->m_stream_id;
           //发送包 1:加入队列发送
           ret = RTMP_SendPacket(rtmp, packet, 1);
           releasePackets(packet);
           if (!ret) {
               LOGE("发送数据失败");
               break;
           }
       }
       releasePackets(packet);
   } while (0);
   if (rtmp) {
       RTMP_Close(rtmp);
       RTMP_Free(rtmp);
   }
   delete url;
   return 0;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_luisliuyi_demo_camera1_LivePusher_native_1init(JNIEnv *env, jobject thiz) {
//  实例化编码层
   videoChannel = new VideoChannel;
   videoChannel->setVideoCallback(callBack);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_luisliuyi_demo_camera1_LivePusher_native_1pushVideo(JNIEnv *env, jobject thiz,
                                                            jbyteArray data_) {
   if (!videoChannel || !readyPushing) {
       return;
   }
   jbyte *data = env->GetByteArrayElements(data_, NULL);
   videoChannel->encodeData(data);
   env->ReleaseByteArrayElements(data_, data, 0);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_luisliuyi_demo_camera1_LivePusher_native_1start(JNIEnv *env, jobject thiz, jstring path_) {
   if (isStart) {
       return;
   }
   const char *path = env->GetStringUTFChars(path_, 0);
   char *url = new char[strlen(path) + 1];
   strcpy(url, path);
//    开始直播
   isStart = 1;
//开子线程链接B站服务器
   pthread_create(&pid, 0, start, url);
   env->ReleaseStringUTFChars(path_, path);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_luisliuyi_demo_camera1_LivePusher_native_1setVideoEncInfo(JNIEnv *env, jobject thiz,
                                                                  jint width, jint height,
                                                                  jint fps, jint bitrate) {
   if (videoChannel) {
       videoChannel->setVideoEncInfo(width, height, fps, bitrate);
   }
}
extern "C"
JNIEXPORT void JNICALL
Java_com_luisliuyi_demo_camera1_LivePusher_native_1stop(JNIEnv *env, jobject thiz) {
   // TODO: implement native_stop()
}extern "C"
JNIEXPORT void JNICALL
Java_com_luisliuyi_demo_camera1_LivePusher_native_1release(JNIEnv *env, jobject thiz) {
   if(rtmp) {
       RTMP_Close(rtmp);
       RTMP_Free(rtmp);
       rtmp = 0;
   }
   if (videoChannel) {
       delete (videoChannel);
       videoChannel = 0;
   }
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_luisliuyi_demo_camera1_LivePusher_initAudioEnc(JNIEnv *env, jobject thiz, jint sample_rate,
                                                       jint channels) {
   audioChannel = new AudioChannel();
   audioChannel->setCallback(callBack);
   audioChannel->openCodec(sample_rate, channels);
   return audioChannel->getInputByteNum();
}

extern "C"
JNIEXPORT void JNICALL
Java_com_luisliuyi_demo_camera1_LivePusher_nativeSendAudio(JNIEnv *env, jobject thiz,
                                                          jbyteArray buffer, jint len) {
   jbyte *data=env->GetByteArrayElements(buffer, 0);
   audioChannel->encode(reinterpret_cast<int32_t *>(data), len);
   env->ReleaseByteArrayElements(buffer, data, 0);
}

五.VideoChannel.cpp

void VideoChannel::setVideoEncInfo(int width, int height, int fps, int bitrate) {
   mWidth = width;
   mHeight = height;
   mFps = fps;
   mBitrate = bitrate;

   ySize = width * height;
   uvSize = ySize / 4;

   if (videoCodec) {
       x264_encoder_close(videoCodec);
       videoCodec = 0;
   }

   x264_param_t param;
   x264_param_default_preset(&param, "ultrafast", "zerolatency"); //编码器 速度
   param.i_level_idc = 32;//编码等级
   param.i_csp = X264_CSP_I420;//    选取显示格式
   param.i_width = width;
   param.i_height = height;
   param.i_bframe = 0;//B帧
   param.rc.i_rc_method = X264_RC_ABR; //cpu  ABR 平均
   param.rc.i_bitrate = bitrate / 1024;
   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.b_vfr_input = 0;//用fps而不是时间戳来计算帧间距离
   param.i_keyint_max = fps * 2;//I帧间隔
   param.b_repeat_headers = 1;// 是否复制sps和pps放在每个关键帧的前面 该参数设置是让每个关键帧(I帧)都附带sps/pps。
   param.i_threads = 1;//多线程
   x264_param_apply_profile(&param, "baseline");

   //打开编码器
   videoCodec = x264_encoder_open(&param);

   pic_in = new x264_picture_t;
   x264_picture_alloc(pic_in, X264_CSP_I420, width, height);
}

void VideoChannel::encodeData(int8_t *data) {
   memcpy(pic_in->img.plane[0], data, ySize);//y数据
   for (int i = 0; i < uvSize; ++i) {
       *(pic_in->img.plane[1] + i) = *(data + ySize + i * 2 + 1);//u数据
       *(pic_in->img.plane[2] + i) = *(data + ySize + i * 2);//v数据
   }

   int pi_nal;
   x264_nal_t *pp_nals;
   x264_picture_t pic_out;//编码出的数据
   x264_encoder_encode(videoCodec, &pp_nals, &pi_nal, pic_in, &pic_out);
   uint8_t sps[100];
   uint8_t pps[100];
   int sps_len, pps_len;
   if (pi_nal > 0) {
       for (int i = 0; i < pi_nal; ++i) {
           if (pp_nals[i].i_type == NAL_SPS) {
               sps_len = pp_nals[i].i_payload - 4;
               memcpy(sps, pp_nals[i].p_payload + 4, sps_len);
           } else if (pp_nals[i].i_type == NAL_PPS) {
               pps_len = pp_nals[i].i_payload - 4;
               memcpy(pps, pp_nals[i].p_payload + 4, pps_len);
               sendSpsPps(sps, pps, sps_len, pps_len);
           } else{
               //关键帧、非关键帧
               sendFrame(pp_nals[i].i_type,pp_nals[i].i_payload,pp_nals[i].p_payload);
           }
       }
   }

   return;
}

void VideoChannel::sendSpsPps(uint8_t *sps, uint8_t *pps, int sps_len, int pps_len) {
   RTMPPacket *packet = new RTMPPacket;
   int bodysize = 13 + sps_len + 3 + pps_len;
   RTMPPacket_Alloc(packet, bodysize);
   int i = 0;
   //固定头
   packet->m_body[i++] = 0x17;
   //类型
   packet->m_body[i++] = 0x00;
   //composition time 0x000000
   packet->m_body[i++] = 0x00;
   packet->m_body[i++] = 0x00;
   packet->m_body[i++] = 0x00;

   //版本
   packet->m_body[i++] = 0x01;
   //编码规格
   packet->m_body[i++] = sps[1];
   packet->m_body[i++] = sps[2];
   packet->m_body[i++] = sps[3];
   packet->m_body[i++] = 0xFF;

   //整个sps
   packet->m_body[i++] = 0xE1;
   //sps长度
   packet->m_body[i++] = (sps_len >> 8) & 0xff;
   packet->m_body[i++] = sps_len & 0xff;
   memcpy(&packet->m_body[i], sps, sps_len);
   i += sps_len;

   //pps
   packet->m_body[i++] = 0x01;
   packet->m_body[i++] = (pps_len >> 8) & 0xff;
   packet->m_body[i++] = (pps_len) & 0xff;
   memcpy(&packet->m_body[i], pps, pps_len);


   //视频
   packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
   packet->m_nBodySize = bodysize;
   //随意分配一个管道(尽量避开rtmp.c中使用的)
   packet->m_nChannel = 10;
   //sps pps没有时间戳
   packet->m_nTimeStamp = 0;
   //不使用绝对时间
   packet->m_hasAbsTimestamp = 0;
   packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;

   if (this->callback) {
       this->callback(packet);
   }
}

void VideoChannel::sendFrame(int type, int payload, uint8_t *p_payload) {
   //去掉 00 00 00 01 / 00 00 01
   if (p_payload[2] == 0x00){
       payload -= 4;
       p_payload += 4;
   } else if(p_payload[2] == 0x01){
       payload -= 3;
       p_payload += 3;
   }
   RTMPPacket *packet = new RTMPPacket;
   int bodysize = 9 + payload;
   RTMPPacket_Alloc(packet, bodysize);
   RTMPPacket_Reset(packet);
   packet->m_body[0] = 0x27;
   //关键帧
   if (type == NAL_SLICE_IDR) {
       packet->m_body[0] = 0x17;
       LOGE("关键帧");
   }
   //类型
   packet->m_body[1] = 0x01;
   //时间戳
   packet->m_body[2] = 0x00;
   packet->m_body[3] = 0x00;
   packet->m_body[4] = 0x00;
   //数据长度 int 4个字节 相当于把int转成4个字节的byte数组
   packet->m_body[5] = (payload >> 24) & 0xff;
   packet->m_body[6] = (payload >> 16) & 0xff;
   packet->m_body[7] = (payload >> 8) & 0xff;
   packet->m_body[8] = (payload) & 0xff;

   //图片数据
   memcpy(&packet->m_body[9],p_payload,  payload);

   packet->m_hasAbsTimestamp = 0;
   packet->m_nBodySize = bodysize;
   packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
   packet->m_nChannel = 0x10;
   packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
   callback(packet);


}


VideoChannel::VideoChannel() {

}

VideoChannel::~VideoChannel() {
   if (videoCodec) {
       x264_encoder_close(videoCodec);
       videoCodec = 0;
   }
}

void VideoChannel::setVideoCallback(VideoChannel::VideoCallback callback) {
   this->callback = callback;
}


六.AudioChannel.cpp

AudioChannel::AudioChannel() {

}

AudioChannel::~AudioChannel() {

}

void AudioChannel::openCodec(int sampleRate, int channels) {
   unsigned long inputSamples;
   codec=  faacEncOpen(sampleRate, channels, &inputSamples, &maxOutputBytes);
   inputByteNum = inputSamples * 2;
   outputBuffer = static_cast<unsigned char *>(malloc(maxOutputBytes));
   faacEncConfigurationPtr configurationPtr = faacEncGetCurrentConfiguration(codec);
   configurationPtr->mpegVersion = MPEG4;
   configurationPtr->aacObjectType = LOW;//编码等级
   configurationPtr->outputFormat = 0;//输出aac裸流数据
   configurationPtr->inputFormat = FAAC_INPUT_16BIT;//采样位数
   faacEncSetConfiguration(codec, configurationPtr);
}

void AudioChannel::encode(int32_t *data, int len) {
   //将pcm数据编码成aac数据
   int bytelen=faacEncEncode(codec, data, len, outputBuffer, maxOutputBytes);
   if (bytelen > 0) {
       RTMPPacket *packet = new RTMPPacket;
       RTMPPacket_Alloc(packet, bytelen + 2);
       packet->m_body[0] = 0xAF;
       packet->m_body[1] = 0x01;
       memcpy(&packet->m_body[2], outputBuffer, bytelen);
       packet->m_hasAbsTimestamp = 0;
       packet->m_nBodySize = bytelen + 2;
       packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
       packet->m_nChannel = 0x11;
       packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
       if (callback) {
//            callback(packet);
       } else {
           LOGE("callback == null");
       }
   }
}

RTMPPacket *AudioChannel::getAudioConfig() {
   u_char *buf;
   u_long len;
   faacEncGetDecoderSpecificInfo(codec, &buf, &len);//头帧的内容   {0x12 0x08}
   RTMPPacket *packet = new RTMPPacket;
   RTMPPacket_Alloc(packet, len + 2);
   packet->m_body[0] = 0xAF;
   packet->m_body[1] = 0x00;
   memcpy(&packet->m_body[2], buf, len);
   packet->m_hasAbsTimestamp = 0;
   packet->m_nBodySize = len + 2;
   packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
   packet->m_nChannel = 0x11;
   packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
   return packet;
}

七.代码地址

https://gitee.com/luisliuyi/android-camerax-x264-faac-rtmp.git

相关文章

网友评论

      本文标题:Android Media 13 --- camerax+x26

      本文链接:https://www.haomeiwen.com/subject/qvycyrtx.html