parent
10ad4172d4
commit
a76ce7e52f
@ -1,490 +0,0 @@ |
|||||||
#include <jni.h> |
|
||||||
#include <string.h> |
|
||||||
#include "include/x264/x264.h" |
|
||||||
#include <android/log.h> |
|
||||||
#include "include/rtmp/rtmp.h" |
|
||||||
#include "include/faac/faac.h" |
|
||||||
#include <pthread.h> |
|
||||||
#include "queue.h" |
|
||||||
#include <stdint.h> |
|
||||||
#include <malloc.h> |
|
||||||
|
|
||||||
#define TAG "FrankLive" |
|
||||||
#define LOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, TAG, format, ##__VA_ARGS__) |
|
||||||
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, TAG, format, ##__VA_ARGS__) |
|
||||||
|
|
||||||
x264_picture_t picture_in; |
|
||||||
x264_picture_t picture_out; |
|
||||||
x264_t *video_encode_handle; |
|
||||||
faacEncHandle *audio_encode_handle; |
|
||||||
uint32_t start_time; |
|
||||||
pthread_cond_t cond; |
|
||||||
pthread_mutex_t mutex; |
|
||||||
char *url_path; |
|
||||||
int is_pushing = FALSE; |
|
||||||
unsigned long inputSamples; |
|
||||||
unsigned long maxOutputBytes; |
|
||||||
//子线程回调给Java需要用到JavaVM
|
|
||||||
JavaVM *javaVM; |
|
||||||
//调用类
|
|
||||||
jobject jobject_error; |
|
||||||
|
|
||||||
/***************与Java层对应**************/ |
|
||||||
//视频编码器打开失败
|
|
||||||
const int ERROR_VIDEO_ENCODER_OPEN = 0x01; |
|
||||||
//视频帧编码失败
|
|
||||||
const int ERROR_VIDEO_ENCODE = 0x02; |
|
||||||
//音频编码器打开失败
|
|
||||||
const int ERROR_AUDIO_ENCODER_OPEN = 0x03; |
|
||||||
//音频帧编码失败
|
|
||||||
const int ERROR_AUDIO_ENCODE = 0x04; |
|
||||||
//RTMP连接失败
|
|
||||||
const int ERROR_RTMP_CONNECT = 0x05; |
|
||||||
//RTMP连接流失败
|
|
||||||
const int ERROR_RTMP_CONNECT_STREAM = 0x06; |
|
||||||
//RTMP发送数据包失败
|
|
||||||
const int ERROR_RTMP_SEND_PACKET = 0x07; |
|
||||||
|
|
||||||
/***************与Java层对应**************/ |
|
||||||
|
|
||||||
void add_rtmp_packet(RTMPPacket *pPacket); |
|
||||||
|
|
||||||
void add_x264_body(uint8_t *buf, int len); |
|
||||||
|
|
||||||
void add_x264_key_header(unsigned char sps[100], unsigned char pps[100], int len, int pps_len); |
|
||||||
|
|
||||||
void add_aac_body(unsigned char *buf, int len); |
|
||||||
|
|
||||||
void add_aac_header(); |
|
||||||
|
|
||||||
//当调用System.loadLibrary时,会回调这个方法
|
|
||||||
//jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
|
||||||
// javaVM = vm;
|
|
||||||
// return JNI_VERSION_1_6;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//回调异常给java
|
|
||||||
void throw_error_to_java(int error_code) { |
|
||||||
JNIEnv *env; |
|
||||||
(*javaVM)->AttachCurrentThread(javaVM, &env, NULL); |
|
||||||
jclass jclazz = (*env)->GetObjectClass(env, jobject_error); |
|
||||||
jmethodID jmethod = (*env)->GetMethodID(env, jclazz, "errorFromNative", "(I)V"); |
|
||||||
(*env)->CallVoidMethod(env, jobject_error, jmethod, error_code); |
|
||||||
(*javaVM)->DetachCurrentThread(javaVM); |
|
||||||
} |
|
||||||
|
|
||||||
//推流线程
|
|
||||||
void *push_thread(void *args) { |
|
||||||
//建立RTMP连接
|
|
||||||
RTMP *rtmp = RTMP_Alloc(); |
|
||||||
if (!rtmp) { |
|
||||||
LOGE("RTMP_Alloc fail..."); |
|
||||||
goto end; |
|
||||||
} |
|
||||||
RTMP_Init(rtmp); |
|
||||||
RTMP_SetupURL(rtmp, url_path); |
|
||||||
LOGI("url_path=%s", url_path); |
|
||||||
RTMP_EnableWrite(rtmp); |
|
||||||
rtmp->Link.timeout = 10; |
|
||||||
if (!RTMP_Connect(rtmp, NULL)) { |
|
||||||
LOGE("RTMP_Connect fail..."); |
|
||||||
throw_error_to_java(ERROR_RTMP_CONNECT); |
|
||||||
goto end; |
|
||||||
} |
|
||||||
LOGI("RTMP_Connect success..."); |
|
||||||
if (!RTMP_ConnectStream(rtmp, 0)) { |
|
||||||
LOGE("RTMP_ConnectStream fail..."); |
|
||||||
throw_error_to_java(ERROR_RTMP_CONNECT_STREAM); |
|
||||||
goto end; |
|
||||||
} |
|
||||||
LOGI("RTMP_ConnectStream success..."); |
|
||||||
|
|
||||||
//开始计时
|
|
||||||
start_time = RTMP_GetTime(); |
|
||||||
is_pushing = TRUE; |
|
||||||
//发送一个ACC HEADER
|
|
||||||
add_aac_header(); |
|
||||||
//循环推流
|
|
||||||
while (is_pushing) { |
|
||||||
pthread_mutex_lock(&mutex); |
|
||||||
pthread_cond_wait(&cond, &mutex); |
|
||||||
//从队头去一个RTMP包出来
|
|
||||||
RTMPPacket *packet = queue_get_first(); |
|
||||||
if (packet) { |
|
||||||
queue_delete_first(); |
|
||||||
//发送rtmp包,true代表rtmp内部有缓存
|
|
||||||
int ret = RTMP_SendPacket(rtmp, packet, TRUE); |
|
||||||
if (!ret) { |
|
||||||
LOGE("RTMP_SendPacket fail..."); |
|
||||||
RTMPPacket_Free(packet); |
|
||||||
pthread_mutex_unlock(&mutex); |
|
||||||
throw_error_to_java(ERROR_RTMP_SEND_PACKET); |
|
||||||
goto end; |
|
||||||
} |
|
||||||
RTMPPacket_Free(packet); |
|
||||||
} |
|
||||||
pthread_mutex_unlock(&mutex); |
|
||||||
} |
|
||||||
end: |
|
||||||
LOGI("free all the thing about rtmp..."); |
|
||||||
RTMP_Close(rtmp); |
|
||||||
free(rtmp); |
|
||||||
free(url_path); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL |
|
||||||
Java_com_frank_live_LiveUtil_native_1start(JNIEnv *env, jobject instance, jstring url_) { |
|
||||||
const char *url = (*env)->GetStringUTFChars(env, url_, 0); |
|
||||||
url_path = malloc(strlen(url) + 1); |
|
||||||
memset(url_path, 0, strlen(url) + 1); |
|
||||||
memcpy(url_path, url, strlen(url)); |
|
||||||
//创建队列
|
|
||||||
create_queue(); |
|
||||||
//初始化互斥锁和条件变量
|
|
||||||
pthread_mutex_init(&mutex, NULL); |
|
||||||
pthread_cond_init(&cond, NULL); |
|
||||||
pthread_t push_thread_id; |
|
||||||
//创建消费线程推流
|
|
||||||
pthread_create(&push_thread_id, NULL, push_thread, NULL); |
|
||||||
(*env)->ReleaseStringUTFChars(env, url_, url); |
|
||||||
|
|
||||||
jobject_error = (*env)->NewGlobalRef(env, instance); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
//视频编码器x264参数配置
|
|
||||||
JNIEXPORT void JNICALL |
|
||||||
Java_com_frank_live_LiveUtil_setVideoParam(JNIEnv *env, jobject instance, jint width, jint height, |
|
||||||
jint bitRate, jint frameRate) { |
|
||||||
|
|
||||||
x264_param_t param; |
|
||||||
//默认设置
|
|
||||||
x264_param_default_preset(¶m, "ultrafast", "zerolatency"); |
|
||||||
param.i_csp = X264_CSP_I420;//YUV420SP
|
|
||||||
param.i_width = width; |
|
||||||
param.i_height = height; |
|
||||||
param.b_vfr_input = 0;//码率控制不通过timebase和timestamp,通过fps控制
|
|
||||||
param.b_repeat_headers = 1;//每帧传入sps和pps,提高视频纠错能力
|
|
||||||
param.i_level_idc = 51;//最大level:resolution@frameRate
|
|
||||||
param.rc.i_rc_method = X264_RC_CRF;//控制恒定码率 CRF:恒定码率;CQP:恒定质量;ABR:平均码率
|
|
||||||
param.rc.i_bitrate = bitRate;//码率
|
|
||||||
param.rc.i_vbv_max_bitrate = (int) (bitRate * 1.2);//瞬间最大码率
|
|
||||||
param.i_fps_num = (uint32_t) frameRate;//帧率分子
|
|
||||||
param.i_fps_den = 1;//帧率分母
|
|
||||||
param.i_timebase_num = param.i_fps_den;//时间基分子
|
|
||||||
param.i_timebase_den = param.i_fps_num;//时间基分母
|
|
||||||
param.i_threads = 1;//编码线程数
|
|
||||||
//设置profile档次,"baseline"代表没有B帧
|
|
||||||
x264_param_apply_profile(¶m, "baseline"); |
|
||||||
//初始化图像
|
|
||||||
x264_picture_alloc(&picture_in, param.i_csp, param.i_width, param.i_height); |
|
||||||
//打开编码器
|
|
||||||
video_encode_handle = x264_encoder_open(¶m); |
|
||||||
if (video_encode_handle) { |
|
||||||
LOGI("x264_encoder_open success..."); |
|
||||||
} else { |
|
||||||
LOGE("x264_encoder_open fail..."); |
|
||||||
throw_error_to_java(ERROR_VIDEO_ENCODER_OPEN); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//音频编码器FAAC参数配置
|
|
||||||
JNIEXPORT void JNICALL |
|
||||||
Java_com_frank_live_LiveUtil_setAudioParam(JNIEnv *env, jobject instance, jint sampleRate, |
|
||||||
jint numChannels) { |
|
||||||
inputSamples; |
|
||||||
maxOutputBytes; |
|
||||||
audio_encode_handle = faacEncOpen((unsigned long) sampleRate, |
|
||||||
(unsigned int) numChannels, &inputSamples, &maxOutputBytes); |
|
||||||
if (!audio_encode_handle) { |
|
||||||
LOGE("faacEncOpen fail..."); |
|
||||||
throw_error_to_java(ERROR_AUDIO_ENCODER_OPEN); |
|
||||||
return; |
|
||||||
} |
|
||||||
LOGI("faacEncOpen success..."); |
|
||||||
faacEncConfigurationPtr configPtr = faacEncGetCurrentConfiguration(audio_encode_handle); |
|
||||||
configPtr->bandWidth = 0; |
|
||||||
configPtr->mpegVersion = MPEG4;//MPEG版本
|
|
||||||
configPtr->outputFormat = 0;//包含ADTS头
|
|
||||||
configPtr->useTns = 1;//时域噪音控制
|
|
||||||
configPtr->useLfe = 0; |
|
||||||
configPtr->allowMidside = 1; |
|
||||||
configPtr->aacObjectType = LOW; |
|
||||||
configPtr->quantqual = 100;//量化
|
|
||||||
configPtr->shortctl = SHORTCTL_NORMAL; |
|
||||||
int result = faacEncSetConfiguration(audio_encode_handle, configPtr); |
|
||||||
if (result) { |
|
||||||
LOGI("faacEncSetConfiguration success..."); |
|
||||||
} else { |
|
||||||
LOGE("faacEncSetConfiguration fail..."); |
|
||||||
throw_error_to_java(ERROR_AUDIO_ENCODER_OPEN); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//添加RTMPPacket包到队列中
|
|
||||||
void add_rtmp_packet(RTMPPacket *pPacket) { |
|
||||||
pthread_mutex_lock(&mutex); |
|
||||||
if (is_pushing) { |
|
||||||
queue_append_last(pPacket); |
|
||||||
} |
|
||||||
pthread_cond_signal(&cond); |
|
||||||
pthread_mutex_unlock(&mutex); |
|
||||||
} |
|
||||||
|
|
||||||
//添加SPS和PPS header
|
|
||||||
void add_x264_key_header(unsigned char sps[100], unsigned char pps[100], int sps_len, int pps_len) { |
|
||||||
int body_size = 16 + sps_len + pps_len; |
|
||||||
RTMPPacket *packet = malloc(sizeof(RTMPPacket)); |
|
||||||
RTMPPacket_Alloc(packet, body_size); |
|
||||||
RTMPPacket_Reset(packet); |
|
||||||
unsigned char *body = (unsigned char *) packet->m_body; |
|
||||||
int i = 0; |
|
||||||
body[i++] = 0x17;//VideoHeadType 0-3:FrameType(KeyFrame=1);4-7:CodecId(AVC=7)
|
|
||||||
body[i++] = 0x00;//AVC PacketType
|
|
||||||
|
|
||||||
body[i++] = 0x00;//composition type 24bit
|
|
||||||
body[i++] = 0x00; |
|
||||||
body[i++] = 0x00; |
|
||||||
|
|
||||||
body[i++] = 0x01;//AVC decoder configuration record
|
|
||||||
body[i++] = sps[1]; |
|
||||||
body[i++] = sps[2]; |
|
||||||
body[i++] = sps[3]; |
|
||||||
|
|
||||||
body[i++] = 0xFF; |
|
||||||
|
|
||||||
body[i++] = 0xE1;//sps
|
|
||||||
body[i++] = (unsigned char) ((sps_len >> 8) & 0xFF); |
|
||||||
body[i++] = (unsigned char) (sps_len & 0xFF); |
|
||||||
memcpy(&body[i], sps, (size_t) sps_len); |
|
||||||
i += sps_len; |
|
||||||
|
|
||||||
body[i++] = 0x01;//pps
|
|
||||||
body[i++] = (unsigned char) ((pps_len >> 8) & 0xFF); |
|
||||||
body[i++] = (unsigned char) (pps_len & 0xFF); |
|
||||||
memcpy(&body[i], pps, (size_t) pps_len); |
|
||||||
i += pps_len; |
|
||||||
|
|
||||||
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; |
|
||||||
packet->m_nBodySize = (uint32_t) body_size; |
|
||||||
packet->m_nTimeStamp = 0; |
|
||||||
packet->m_hasAbsTimestamp = 0; |
|
||||||
packet->m_nChannel = 0x04; |
|
||||||
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; |
|
||||||
|
|
||||||
add_rtmp_packet(packet); |
|
||||||
} |
|
||||||
|
|
||||||
//添加x264的body
|
|
||||||
void add_x264_body(uint8_t *buf, int len) { |
|
||||||
if (buf[2] == 0x01) {//00 00 01
|
|
||||||
buf += 3; |
|
||||||
len -= 3; |
|
||||||
} else if (buf[3] == 0x01) {//00 00 00 01
|
|
||||||
buf += 4; |
|
||||||
len -= 4; |
|
||||||
} |
|
||||||
int body_size = len + 9;//
|
|
||||||
RTMPPacket *packet = malloc(sizeof(RTMPPacket)); |
|
||||||
RTMPPacket_Alloc(packet, body_size); |
|
||||||
RTMPPacket_Reset(packet); |
|
||||||
unsigned char *body = (unsigned char *) packet->m_body; |
|
||||||
int type = buf[0] & 0x1F; |
|
||||||
if (type == NAL_SLICE_IDR) {//关键帧
|
|
||||||
body[0] = 0x17; |
|
||||||
} else { |
|
||||||
body[0] = 0x27; |
|
||||||
} |
|
||||||
body[1] = 0x01; |
|
||||||
body[2] = 0x00; |
|
||||||
body[3] = 0x00; |
|
||||||
body[4] = 0x00; |
|
||||||
|
|
||||||
body[5] = (unsigned char) ((len >> 24) & 0xFF); |
|
||||||
body[6] = (unsigned char) ((len >> 16) & 0xFF); |
|
||||||
body[7] = (unsigned char) ((len >> 8) & 0xFF); |
|
||||||
body[8] = (unsigned char) (len & 0xFF); |
|
||||||
|
|
||||||
memcpy(&body[9], buf, (size_t) len); |
|
||||||
packet->m_headerType = RTMP_PACKET_SIZE_LARGE; |
|
||||||
packet->m_hasAbsTimestamp = 0; |
|
||||||
packet->m_nBodySize = (uint32_t) body_size; |
|
||||||
packet->m_nChannel = 0x04; |
|
||||||
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; |
|
||||||
packet->m_nTimeStamp = RTMP_GetTime() - start_time; |
|
||||||
|
|
||||||
add_rtmp_packet(packet); |
|
||||||
} |
|
||||||
|
|
||||||
//添加AAC header
|
|
||||||
void add_aac_header() { |
|
||||||
unsigned char *ppBuffer; |
|
||||||
unsigned long pSize; |
|
||||||
faacEncGetDecoderSpecificInfo(audio_encode_handle, &ppBuffer, &pSize); |
|
||||||
int body_size = (int) (2 + pSize); |
|
||||||
RTMPPacket *packet = malloc(sizeof(RTMPPacket)); |
|
||||||
RTMPPacket_Alloc(packet, body_size); |
|
||||||
RTMPPacket_Reset(packet); |
|
||||||
unsigned char *body = (unsigned char *) packet->m_body; |
|
||||||
//两字节头
|
|
||||||
//soundFormat(4bits):10->aac;soundRate(2bits):3->44kHz;soundSize(1bit):1->pcm_16bit;soundType(1bit):1->stereo
|
|
||||||
body[0] = 0xAF; |
|
||||||
//AACPacketType:0表示AAC sequence header
|
|
||||||
body[1] = 0x00; |
|
||||||
|
|
||||||
memcpy(&body[2], ppBuffer, (size_t) pSize); |
|
||||||
packet->m_packetType = RTMP_PACKET_TYPE_AUDIO; |
|
||||||
packet->m_nBodySize = (uint32_t) body_size; |
|
||||||
packet->m_nChannel = 4; |
|
||||||
packet->m_nTimeStamp = 0; |
|
||||||
packet->m_hasAbsTimestamp = 0; |
|
||||||
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; |
|
||||||
add_rtmp_packet(packet); |
|
||||||
free(ppBuffer); |
|
||||||
} |
|
||||||
|
|
||||||
//添加AAC body
|
|
||||||
void add_aac_body(unsigned char *buf, int len) { |
|
||||||
int body_size = 2 + len; |
|
||||||
RTMPPacket *packet = malloc(sizeof(RTMPPacket)); |
|
||||||
RTMPPacket_Alloc(packet, body_size); |
|
||||||
RTMPPacket_Reset(packet); |
|
||||||
unsigned char *body = (unsigned char *) packet->m_body; |
|
||||||
//两字节头
|
|
||||||
//soundFormat(4bits):10->aac;soundRate(2bits):3->44kHz;soundSize(1bit):1->pcm_16bit;soundType(1bit):1->stereo
|
|
||||||
body[0] = 0xAF; |
|
||||||
//PackageType(StreamType):0->AAC带ADTS头;1->AAC RAW
|
|
||||||
body[1] = 0x01; |
|
||||||
|
|
||||||
memcpy(&body[2], buf, (size_t) len); |
|
||||||
packet->m_packetType = RTMP_PACKET_TYPE_AUDIO; |
|
||||||
packet->m_nBodySize = (uint32_t) body_size; |
|
||||||
packet->m_nChannel = 4; |
|
||||||
packet->m_nTimeStamp = RTMP_GetTime() - start_time; |
|
||||||
packet->m_hasAbsTimestamp = 0; |
|
||||||
packet->m_headerType = RTMP_PACKET_SIZE_LARGE; |
|
||||||
add_rtmp_packet(packet); |
|
||||||
free(buf); |
|
||||||
} |
|
||||||
|
|
||||||
//推送音频流
|
|
||||||
JNIEXPORT void JNICALL |
|
||||||
Java_com_frank_live_LiveUtil_pushAudio(JNIEnv *env, jobject instance, jbyteArray data_, |
|
||||||
jint length) { |
|
||||||
jbyte *data = (*env)->GetByteArrayElements(env, data_, NULL); |
|
||||||
int *pcm_buf; |
|
||||||
unsigned char *aac_buf; |
|
||||||
pcm_buf = malloc(inputSamples * sizeof(int)); |
|
||||||
aac_buf = malloc(maxOutputBytes * sizeof(unsigned char)); |
|
||||||
int count = 0; |
|
||||||
unsigned int buffer_size = (unsigned int) (length / 2); |
|
||||||
unsigned short *buf = (unsigned short *) data; |
|
||||||
while (count < buffer_size) { |
|
||||||
int audio_length = (int) inputSamples; |
|
||||||
if ((count + audio_length) >= buffer_size) { |
|
||||||
audio_length = buffer_size - count; |
|
||||||
} |
|
||||||
int i; |
|
||||||
for (i = 0; i < audio_length; i++) { |
|
||||||
//每次从实时的pcm音频队列中读出量化位数为8的pcm数据
|
|
||||||
int sample_byte = ((int16_t *) buf + count)[i]; |
|
||||||
//用8个二进制位来表示一个采样量化点(模数转换)
|
|
||||||
pcm_buf[i] = sample_byte << 8; |
|
||||||
} |
|
||||||
count += inputSamples; |
|
||||||
//调用FAAC编码,返回编码字节数
|
|
||||||
int bytes_len = faacEncEncode(audio_encode_handle, pcm_buf, (unsigned int) audio_length, |
|
||||||
aac_buf, maxOutputBytes); |
|
||||||
if (bytes_len <= 0) { |
|
||||||
// throw_error_to_java(ERROR_AUDIO_ENCODE);
|
|
||||||
LOGE("音频编码失败..."); |
|
||||||
continue; |
|
||||||
} |
|
||||||
add_aac_body(aac_buf, bytes_len); |
|
||||||
} |
|
||||||
(*env)->ReleaseByteArrayElements(env, data_, data, 0); |
|
||||||
if (pcm_buf != NULL) { |
|
||||||
free(pcm_buf); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//推送视频流
|
|
||||||
JNIEXPORT void JNICALL |
|
||||||
Java_com_frank_live_LiveUtil_pushVideoNew(JNIEnv *env, jobject instance, jbyteArray yPlane, |
|
||||||
jbyteArray uPlane, jbyteArray vPlane) { |
|
||||||
jbyte *y_plane = (*env)->GetByteArrayElements(env, yPlane, NULL); |
|
||||||
jbyte *u_plane = (*env)->GetByteArrayElements(env, uPlane, NULL); |
|
||||||
jbyte *v_plane = (*env)->GetByteArrayElements(env, vPlane, NULL); |
|
||||||
jsize y_length = (*env)->GetArrayLength(env, yPlane); |
|
||||||
jsize u_length = (*env)->GetArrayLength(env, uPlane); |
|
||||||
jsize v_length = (*env)->GetArrayLength(env, vPlane); |
|
||||||
|
|
||||||
//直接拷贝
|
|
||||||
memcpy(picture_in.img.plane[0], y_plane, (size_t) y_length); |
|
||||||
memcpy(picture_in.img.plane[1], u_plane, (size_t) u_length); |
|
||||||
memcpy(picture_in.img.plane[2], v_plane, (size_t) v_length); |
|
||||||
|
|
||||||
int i; |
|
||||||
x264_nal_t *nal = NULL; |
|
||||||
int nal_num = -1;//NAL unit个数
|
|
||||||
//调用h264编码
|
|
||||||
if (x264_encoder_encode(video_encode_handle, &nal, &nal_num, &picture_in, &picture_out) < 0) { |
|
||||||
LOGE("x264_encoder_encode fail"); |
|
||||||
throw_error_to_java(ERROR_VIDEO_ENCODE); |
|
||||||
goto end; |
|
||||||
} |
|
||||||
if (nal_num <= 0) { |
|
||||||
LOGE("nal_num <= 0"); |
|
||||||
goto end; |
|
||||||
} |
|
||||||
//使用RTMP推流
|
|
||||||
//关键帧(I帧)加上SPS和PPS
|
|
||||||
int sps_len = 0, pps_len = 0; |
|
||||||
unsigned char sps[100]; |
|
||||||
unsigned char pps[100]; |
|
||||||
memset(sps, 0, 100); |
|
||||||
memset(pps, 0, 100); |
|
||||||
for (i = 0; i < nal_num; ++i) { |
|
||||||
if (nal[i].i_type == NAL_SPS) {//sps
|
|
||||||
sps_len = nal[i].i_payload - 4; |
|
||||||
memcpy(sps, nal[i].p_payload + 4, (size_t) 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, (size_t) pps_len); |
|
||||||
add_x264_key_header(sps, pps, sps_len, pps_len); |
|
||||||
} else { |
|
||||||
add_x264_body(nal[i].p_payload, nal[i].i_payload); |
|
||||||
} |
|
||||||
} |
|
||||||
end: |
|
||||||
(*env)->ReleaseByteArrayElements(env, yPlane, y_plane, 0); |
|
||||||
(*env)->ReleaseByteArrayElements(env, uPlane, u_plane, 0); |
|
||||||
(*env)->ReleaseByteArrayElements(env, vPlane, v_plane, 0); |
|
||||||
} |
|
||||||
|
|
||||||
//停止推流
|
|
||||||
JNIEXPORT void JNICALL |
|
||||||
Java_com_frank_live_LiveUtil_native_1stop(JNIEnv *env, jobject instance) { |
|
||||||
is_pushing = FALSE; |
|
||||||
LOGI("native_1stop"); |
|
||||||
} |
|
||||||
|
|
||||||
//释放所有资源
|
|
||||||
JNIEXPORT void JNICALL |
|
||||||
Java_com_frank_live_LiveUtil_native_1release(JNIEnv *env, jobject instance) { |
|
||||||
//清除x264的picture缓存
|
|
||||||
x264_picture_clean(&picture_in); |
|
||||||
x264_picture_clean(&picture_out); |
|
||||||
//关闭音视频编码器
|
|
||||||
x264_encoder_close(video_encode_handle); |
|
||||||
faacEncClose(audio_encode_handle); |
|
||||||
//删除全局引用
|
|
||||||
(*env)->DeleteGlobalRef(env, jobject_error); |
|
||||||
(*javaVM)->DestroyJavaVM(javaVM); |
|
||||||
//销毁互斥锁和条件变量
|
|
||||||
pthread_cond_destroy(&cond); |
|
||||||
pthread_mutex_destroy(&mutex); |
|
||||||
//退出线程
|
|
||||||
pthread_exit(0); |
|
||||||
} |
|
@ -1,218 +0,0 @@ |
|||||||
#include <stdio.h> |
|
||||||
#include <malloc.h> |
|
||||||
|
|
||||||
typedef struct queue_node { |
|
||||||
struct queue_node* prev; |
|
||||||
struct queue_node* next; |
|
||||||
void *p;//节点的值
|
|
||||||
} node; |
|
||||||
|
|
||||||
// 表头
|
|
||||||
static node *phead = NULL; |
|
||||||
static int count = 0; |
|
||||||
|
|
||||||
static node* create_node(void *pval) { |
|
||||||
node *pnode = NULL; |
|
||||||
pnode = (node *) malloc(sizeof(node)); |
|
||||||
if (pnode) { |
|
||||||
// 默认的,pnode的前一节点和后一节点都指向它自身
|
|
||||||
pnode->prev = pnode->next = pnode; |
|
||||||
// 节点的值为pval
|
|
||||||
pnode->p = pval; |
|
||||||
} |
|
||||||
return pnode; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建双向链表 |
|
||||||
* @return 成功,返回0;否则,返回-1。 |
|
||||||
*/ |
|
||||||
int create_queue() { |
|
||||||
phead = create_node(NULL); |
|
||||||
if (!phead) { |
|
||||||
return -1; |
|
||||||
} |
|
||||||
count = 0; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断双向链表是否为空 |
|
||||||
* @return 0为空 |
|
||||||
*/ |
|
||||||
int queue_is_empty() { |
|
||||||
return count == 0; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取双向链表的大小 |
|
||||||
* @return size |
|
||||||
*/ |
|
||||||
int queue_size() { |
|
||||||
return count; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取链表中指定位置的节点 |
|
||||||
*/ |
|
||||||
|
|
||||||
static node* get_node(int index) { |
|
||||||
if (index < 0 || index >= count) { |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
if (index <= (count / 2)) { |
|
||||||
int i = 0; |
|
||||||
node *pnode = phead->next; |
|
||||||
while ((i++) < index) |
|
||||||
pnode = pnode->next; |
|
||||||
return pnode; |
|
||||||
} |
|
||||||
int j = 0; |
|
||||||
int rindex = count - index - 1; |
|
||||||
node *rnode = phead->prev; |
|
||||||
while ((j++) < rindex) |
|
||||||
rnode = rnode->prev; |
|
||||||
return rnode; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取链表中指定位置的元素 |
|
||||||
* @param index index |
|
||||||
* @return 指定位置的元素 |
|
||||||
*/ |
|
||||||
void* queue_get(int index) { |
|
||||||
node *pindex = get_node(index); |
|
||||||
if (!pindex) { |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
return pindex->p; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取链表中第1个元素的值 |
|
||||||
* @return 第1个元素的值 |
|
||||||
*/ |
|
||||||
void* queue_get_first() { |
|
||||||
return queue_get(0); |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取链表中最后1个元素的值 |
|
||||||
* @return 最后1个元素的值 |
|
||||||
*/ |
|
||||||
void* queue_get_last() { |
|
||||||
return queue_get(count - 1); |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入到index位置 |
|
||||||
* @param index index |
|
||||||
* @param pval pval |
|
||||||
* @return 是否插入成功 |
|
||||||
*/ |
|
||||||
int queue_insert(int index, void* pval) { |
|
||||||
// 插入表头
|
|
||||||
if (index == 0) |
|
||||||
return queue_insert_first(pval); |
|
||||||
// 获取要插入的位置对应的节点
|
|
||||||
node *pindex = get_node(index); |
|
||||||
if (!pindex) |
|
||||||
return -1; |
|
||||||
// 创建“节点”
|
|
||||||
node *pnode = create_node(pval); |
|
||||||
if (!pnode) |
|
||||||
return -1; |
|
||||||
pnode->prev = pindex->prev; |
|
||||||
pnode->next = pindex; |
|
||||||
pindex->prev->next = pnode; |
|
||||||
pindex->prev = pnode; |
|
||||||
// 节点个数+1
|
|
||||||
count++; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入到链表头 |
|
||||||
* @param pval |
|
||||||
* @return 是否插入成功 |
|
||||||
*/ |
|
||||||
int queue_insert_first(void *pval) { |
|
||||||
node *pnode = create_node(pval); |
|
||||||
if (!pnode) |
|
||||||
return -1; |
|
||||||
pnode->prev = phead; |
|
||||||
pnode->next = phead->next; |
|
||||||
phead->next->prev = pnode; |
|
||||||
phead->next = pnode; |
|
||||||
count++; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入链表尾 |
|
||||||
* @param pval |
|
||||||
* @return 是否插入成功 |
|
||||||
*/ |
|
||||||
int queue_append_last(void *pval) { |
|
||||||
node *pnode = create_node(pval); |
|
||||||
if (!pnode) |
|
||||||
return -1; |
|
||||||
pnode->next = phead; |
|
||||||
pnode->prev = phead->prev; |
|
||||||
phead->prev->next = pnode; |
|
||||||
phead->prev = pnode; |
|
||||||
count++; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除双向链表中index位置的节点 |
|
||||||
* @param index index |
|
||||||
* @return 成功,返回0;否则,返回-1 |
|
||||||
*/ |
|
||||||
int queue_delete(int index) { |
|
||||||
node *pindex = get_node(index); |
|
||||||
if (!pindex) { |
|
||||||
return -1; |
|
||||||
} |
|
||||||
pindex->next->prev = pindex->prev; |
|
||||||
pindex->prev->next = pindex->next; |
|
||||||
free(pindex); |
|
||||||
count--; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除第一个节点 |
|
||||||
*/ |
|
||||||
int queue_delete_first() { |
|
||||||
return queue_delete(0); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* 删除最后一个节点 |
|
||||||
*/ |
|
||||||
int queue_delete_last() { |
|
||||||
return queue_delete(count - 1); |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* 释放双向链表 |
|
||||||
* @return 成功,返回0;否则,返回-1。 |
|
||||||
*/ |
|
||||||
int destroy_queue() { |
|
||||||
if (!phead) { |
|
||||||
return -1; |
|
||||||
} |
|
||||||
node *pnode = phead->next; |
|
||||||
node *ptmp = NULL; |
|
||||||
while (pnode != phead) { |
|
||||||
ptmp = pnode; |
|
||||||
pnode = pnode->next; |
|
||||||
free(ptmp); |
|
||||||
} |
|
||||||
free(phead); |
|
||||||
phead = NULL; |
|
||||||
count = 0; |
|
||||||
return 0; |
|
||||||
} |
|
@ -1,29 +0,0 @@ |
|||||||
#ifndef _QUQEUE_H |
|
||||||
#define _QUQEUE_H |
|
||||||
// 新建“双向链表”。成功,返回表头;否则,返回NULL
|
|
||||||
extern int create_queue(); |
|
||||||
// 撤销“双向链表”。成功,返回0;否则,返回-1
|
|
||||||
extern int destroy_queue(); |
|
||||||
// “双向链表是否为空”。为空的话返回1;否则,返回0。
|
|
||||||
extern int queue_is_empty(); |
|
||||||
// 返回“双向链表的大小”
|
|
||||||
extern int queue_size(); |
|
||||||
// 获取“双向链表中第index位置的元素”。成功,返回节点指针;否则,返回NULL。
|
|
||||||
extern void* queue_get(int index); |
|
||||||
// 获取“双向链表中第1个元素”。成功,返回节点指针;否则,返回NULL。
|
|
||||||
extern void* queue_get_first(); |
|
||||||
// 获取“双向链表中最后1个元素”。成功,返回节点指针;否则,返回NULL。
|
|
||||||
extern void* queue_get_last(); |
|
||||||
// 将“value”插入到index位置。成功,返回0;否则,返回-1。
|
|
||||||
extern int queue_insert(int index, void *pval); |
|
||||||
// 将“value”插入到表头位置。成功,返回0;否则,返回-1。
|
|
||||||
extern int queue_insert_first(void *pval); |
|
||||||
// 将“value”插入到末尾位置。成功,返回0;否则,返回-1。
|
|
||||||
extern int queue_append_last(void *pval); |
|
||||||
// 删除“双向链表中index位置的节点”。成功,返回0;否则,返回-1
|
|
||||||
extern int queue_delete(int index); |
|
||||||
// 删除第一个节点。成功,返回0;否则,返回-1
|
|
||||||
extern int queue_delete_first(); |
|
||||||
// 删除组后一个节点。成功,返回0;否则,返回-1
|
|
||||||
extern int queue_delete_last(); |
|
||||||
#endif//_QUQEUE_H
|
|
@ -1,120 +0,0 @@ |
|||||||
package com.frank.live; |
|
||||||
|
|
||||||
import com.frank.live.listener.LiveStateChangeListener; |
|
||||||
|
|
||||||
/** |
|
||||||
* 直播核心类 |
|
||||||
* Created by frank on 2017/8/15. |
|
||||||
*/ |
|
||||||
|
|
||||||
public class LiveUtil { |
|
||||||
|
|
||||||
static { |
|
||||||
System.loadLibrary("live"); |
|
||||||
} |
|
||||||
|
|
||||||
private native int native_start(String url); |
|
||||||
|
|
||||||
private native void setVideoParam(int width, int height, int bitRate, int frameRate); |
|
||||||
|
|
||||||
private native void setAudioParam(int sampleRate, int numChannels); |
|
||||||
|
|
||||||
private native void pushVideoNew(byte[] yPlane, byte[] uPlane, byte[] vPlane); |
|
||||||
|
|
||||||
private native void pushAudio(byte[] data, int length); |
|
||||||
|
|
||||||
private native void native_stop(); |
|
||||||
|
|
||||||
private native void native_release(); |
|
||||||
|
|
||||||
//视频编码器打开失败
|
|
||||||
private final static int ERROR_VIDEO_ENCODER_OPEN = 0x01; |
|
||||||
//视频帧编码失败
|
|
||||||
private final static int ERROR_VIDEO_ENCODE = 0x02; |
|
||||||
//音频编码器打开失败
|
|
||||||
private final static int ERROR_AUDIO_ENCODER_OPEN = 0x03; |
|
||||||
//音频帧编码失败
|
|
||||||
private final static int ERROR_AUDIO_ENCODE = 0x04; |
|
||||||
//RTMP连接失败
|
|
||||||
private final static int ERROR_RTMP_CONNECT = 0x05; |
|
||||||
//RTMP连接流失败
|
|
||||||
private final static int ERROR_RTMP_CONNECT_STREAM = 0x06; |
|
||||||
//RTMP发送数据包失败
|
|
||||||
private final static int ERROR_RTMP_SEND_PACKAT = 0x07; |
|
||||||
|
|
||||||
private LiveStateChangeListener liveStateChangeListener; |
|
||||||
|
|
||||||
public LiveUtil() { |
|
||||||
} |
|
||||||
|
|
||||||
public int startPush(String url) { |
|
||||||
return native_start(url); |
|
||||||
} |
|
||||||
|
|
||||||
public void setVideoParams(int width, int height, int bitRate, int frameRate) { |
|
||||||
setVideoParam(width, height, bitRate, frameRate); |
|
||||||
} |
|
||||||
|
|
||||||
public void setAudioParams(int sampleRate, int numChannels) { |
|
||||||
setAudioParam(sampleRate, numChannels); |
|
||||||
} |
|
||||||
|
|
||||||
public void pushVideoData(byte[] yPlane, byte[] uPlane, byte[] vPlane) { |
|
||||||
pushVideoNew(yPlane, uPlane, vPlane); |
|
||||||
} |
|
||||||
|
|
||||||
public void pushAudioData(byte[] data, int length) { |
|
||||||
pushAudio(data, length); |
|
||||||
} |
|
||||||
|
|
||||||
public void stopPush() { |
|
||||||
native_stop(); |
|
||||||
} |
|
||||||
|
|
||||||
public void release() { |
|
||||||
native_release(); |
|
||||||
} |
|
||||||
|
|
||||||
public void setOnLiveStateChangeListener(LiveStateChangeListener liveStateChangeListener) { |
|
||||||
this.liveStateChangeListener = liveStateChangeListener; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 当native报错时,回调这个方法 |
|
||||||
* |
|
||||||
* @param errCode errCode |
|
||||||
*/ |
|
||||||
public void errorFromNative(int errCode) { |
|
||||||
//直播出错了,应该停止推流
|
|
||||||
stopPush(); |
|
||||||
if (liveStateChangeListener != null) { |
|
||||||
String msg = ""; |
|
||||||
switch (errCode) { |
|
||||||
case ERROR_VIDEO_ENCODER_OPEN: |
|
||||||
msg = "视频编码器打开失败..."; |
|
||||||
break; |
|
||||||
case ERROR_VIDEO_ENCODE: |
|
||||||
msg = "视频帧编码失败..."; |
|
||||||
break; |
|
||||||
case ERROR_AUDIO_ENCODER_OPEN: |
|
||||||
msg = "音频编码器打开失败..."; |
|
||||||
break; |
|
||||||
case ERROR_AUDIO_ENCODE: |
|
||||||
msg = "音频帧编码失败..."; |
|
||||||
break; |
|
||||||
case ERROR_RTMP_CONNECT: |
|
||||||
msg = "RTMP连接失败..."; |
|
||||||
break; |
|
||||||
case ERROR_RTMP_CONNECT_STREAM: |
|
||||||
msg = "RTMP连接流失败..."; |
|
||||||
break; |
|
||||||
case ERROR_RTMP_SEND_PACKAT: |
|
||||||
msg = "RTMP发送数据包失败..."; |
|
||||||
break; |
|
||||||
default: |
|
||||||
break; |
|
||||||
} |
|
||||||
liveStateChangeListener.onError(msg); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,91 +0,0 @@ |
|||||||
package com.frank.live.Push; |
|
||||||
|
|
||||||
import android.media.AudioRecord; |
|
||||||
import android.media.MediaRecorder; |
|
||||||
import android.util.Log; |
|
||||||
import com.frank.live.LiveUtil; |
|
||||||
import com.frank.live.param.AudioParam; |
|
||||||
|
|
||||||
/** |
|
||||||
* 音频推流 |
|
||||||
* Created by frank on 2018/1/28. |
|
||||||
*/ |
|
||||||
|
|
||||||
public class AudioPusher extends Pusher { |
|
||||||
|
|
||||||
private AudioRecord audioRecord; |
|
||||||
private boolean isPushing; |
|
||||||
private int minBufferSize; |
|
||||||
private LiveUtil liveUtil; |
|
||||||
private boolean isMute; |
|
||||||
|
|
||||||
AudioPusher(AudioParam audioParam, LiveUtil liveUtil){ |
|
||||||
this.liveUtil = liveUtil; |
|
||||||
initAudioRecord(audioParam); |
|
||||||
liveUtil.setAudioParams(audioParam.getSampleRate(), audioParam.getNumChannels()); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void startPush() { |
|
||||||
isPushing = true; |
|
||||||
new AudioRecordThread(audioRecord).start(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void stopPush() { |
|
||||||
isPushing = false; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void release() { |
|
||||||
stopPush(); |
|
||||||
if(audioRecord != null){ |
|
||||||
audioRecord.release(); |
|
||||||
audioRecord = null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void initAudioRecord(AudioParam audioParam){ |
|
||||||
minBufferSize = AudioRecord.getMinBufferSize(audioParam.getSampleRate(), |
|
||||||
audioParam.getChannelConfig(), audioParam.getAudioFormat()); |
|
||||||
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, audioParam.getSampleRate(), |
|
||||||
audioParam.getChannelConfig(), audioParam.getAudioFormat(), minBufferSize); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 录音线程:循环读取音频数据送去native层编码 |
|
||||||
*/ |
|
||||||
class AudioRecordThread extends Thread{ |
|
||||||
private AudioRecord audioRecord; |
|
||||||
|
|
||||||
AudioRecordThread(AudioRecord audioRecord){ |
|
||||||
this.audioRecord = audioRecord; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
super.run(); |
|
||||||
audioRecord.startRecording(); |
|
||||||
while (isPushing){//处于推流状态
|
|
||||||
if(!isMute){//如果不静音,才推音频流
|
|
||||||
byte[] audioBuffer = new byte[minBufferSize]; |
|
||||||
int length = audioRecord.read(audioBuffer, 0, audioBuffer.length); |
|
||||||
if(length > 0){ |
|
||||||
// Log.i("AudioPusher", "is recording...");
|
|
||||||
liveUtil.pushAudioData(audioBuffer, length); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
audioRecord.stop(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 设置静音 |
|
||||||
* @param isMute 是否静音 |
|
||||||
*/ |
|
||||||
void setMute(boolean isMute){ |
|
||||||
this.isMute = isMute; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,74 +0,0 @@ |
|||||||
package com.frank.live.Push; |
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import android.util.Log; |
|
||||||
import android.view.TextureView; |
|
||||||
|
|
||||||
import com.frank.live.LiveUtil; |
|
||||||
import com.frank.live.listener.LiveStateChangeListener; |
|
||||||
import com.frank.live.param.AudioParam; |
|
||||||
import com.frank.live.param.VideoParam; |
|
||||||
|
|
||||||
/** |
|
||||||
* 音视频推流 |
|
||||||
* Created by frank on 2018/1/28. |
|
||||||
*/ |
|
||||||
|
|
||||||
public class LivePusher { |
|
||||||
|
|
||||||
private VideoPusherNew videoPusher; |
|
||||||
private AudioPusher audioPusher; |
|
||||||
private LiveUtil liveUtil; |
|
||||||
|
|
||||||
public LivePusher(TextureView textureView, VideoParam videoParam, AudioParam audioParam, Context context) { |
|
||||||
liveUtil = new LiveUtil(); |
|
||||||
videoPusher = new VideoPusherNew(textureView, videoParam, liveUtil, context); |
|
||||||
audioPusher = new AudioPusher(audioParam, liveUtil); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 开始推流 |
|
||||||
*/ |
|
||||||
public void startPush(String liveUrl, LiveStateChangeListener liveStateChangeListener) { |
|
||||||
videoPusher.startPush(); |
|
||||||
audioPusher.startPush(); |
|
||||||
liveUtil.setOnLiveStateChangeListener(liveStateChangeListener); |
|
||||||
int result = liveUtil.startPush(liveUrl); |
|
||||||
Log.i("LivePusher", "startPush=" + (result == 0 ? "success" : "fail")); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 停止推流 |
|
||||||
*/ |
|
||||||
public void stopPush() { |
|
||||||
videoPusher.stopPush(); |
|
||||||
audioPusher.stopPush(); |
|
||||||
liveUtil.stopPush(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 切换摄像头 |
|
||||||
*/ |
|
||||||
public void switchCamera() { |
|
||||||
videoPusher.switchCamera(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 释放资源 |
|
||||||
*/ |
|
||||||
public void release() { |
|
||||||
videoPusher.release(); |
|
||||||
audioPusher.release(); |
|
||||||
liveUtil.release(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 设置静音 |
|
||||||
* |
|
||||||
* @param isMute 是否静音 |
|
||||||
*/ |
|
||||||
public void setMute(boolean isMute) { |
|
||||||
audioPusher.setMute(isMute); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
package com.frank.live.Push; |
|
||||||
|
|
||||||
/** |
|
||||||
* |
|
||||||
* Created by frank on 2018/1/28. |
|
||||||
*/ |
|
||||||
|
|
||||||
public abstract class Pusher { |
|
||||||
public abstract void startPush(); |
|
||||||
public abstract void stopPush(); |
|
||||||
public abstract void release(); |
|
||||||
} |
|
@ -1,157 +0,0 @@ |
|||||||
package com.frank.live.Push; |
|
||||||
|
|
||||||
import android.app.Activity; |
|
||||||
import android.content.Context; |
|
||||||
import android.graphics.Point; |
|
||||||
import android.graphics.SurfaceTexture; |
|
||||||
import android.util.Log; |
|
||||||
import android.util.Size; |
|
||||||
import android.view.TextureView; |
|
||||||
|
|
||||||
import com.frank.live.LiveUtil; |
|
||||||
import com.frank.live.camera2.Camera2Helper; |
|
||||||
import com.frank.live.camera2.Camera2Listener; |
|
||||||
import com.frank.live.param.VideoParam; |
|
||||||
|
|
||||||
/** |
|
||||||
* 视频推流:使用Camera2 |
|
||||||
* Created by frank on 2019/12/18. |
|
||||||
*/ |
|
||||||
|
|
||||||
public class VideoPusherNew extends Pusher implements TextureView.SurfaceTextureListener, Camera2Listener { |
|
||||||
private final static String TAG = VideoPusherNew.class.getSimpleName(); |
|
||||||
|
|
||||||
private VideoParam mVideoParam; |
|
||||||
private Camera2Helper camera2Helper; |
|
||||||
private boolean isPushing; |
|
||||||
private LiveUtil mLiveUtil; |
|
||||||
private TextureView mTextureView; |
|
||||||
private Context mContext; |
|
||||||
|
|
||||||
VideoPusherNew(TextureView textureView, VideoParam videoParam, LiveUtil liveUtil, Context context) { |
|
||||||
this.mTextureView = textureView; |
|
||||||
this.mVideoParam = videoParam; |
|
||||||
this.mLiveUtil = liveUtil; |
|
||||||
this.mContext = context; |
|
||||||
mTextureView.setSurfaceTextureListener(this); |
|
||||||
// liveUtil.setVideoParams(videoParam.getWidth(), videoParam.getHeight(),
|
|
||||||
// videoParam.getBitRate(), videoParam.getFrameRate());
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Override |
|
||||||
public void startPush() { |
|
||||||
isPushing = true; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void stopPush() { |
|
||||||
isPushing = false; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void release() { |
|
||||||
stopPush(); |
|
||||||
releasePreview(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 开始预览 |
|
||||||
*/ |
|
||||||
private void startPreview() { |
|
||||||
int rotateDegree = 0; |
|
||||||
if (mContext instanceof Activity) { |
|
||||||
rotateDegree = ((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation(); |
|
||||||
} |
|
||||||
Log.e(TAG, "preview width=" + mTextureView.getWidth() + "--height=" + mTextureView.getHeight()); |
|
||||||
camera2Helper = new Camera2Helper.Builder() |
|
||||||
.cameraListener(this) |
|
||||||
.specificCameraId(Camera2Helper.CAMERA_ID_BACK) |
|
||||||
.context(mContext.getApplicationContext()) |
|
||||||
.previewOn(mTextureView) |
|
||||||
// .previewViewSize(new Point(mTextureView.getWidth(), mTextureView.getHeight()))
|
|
||||||
.previewViewSize(new Point(mVideoParam.getWidth(), mVideoParam.getHeight())) |
|
||||||
.rotation(rotateDegree) |
|
||||||
.build(); |
|
||||||
camera2Helper.start(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 停止预览 |
|
||||||
*/ |
|
||||||
private void stopPreview() { |
|
||||||
if (camera2Helper != null) { |
|
||||||
camera2Helper.stop(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 释放资源 |
|
||||||
*/ |
|
||||||
private void releasePreview() { |
|
||||||
if (camera2Helper != null) { |
|
||||||
camera2Helper.stop(); |
|
||||||
camera2Helper.release(); |
|
||||||
camera2Helper = null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 切换摄像头 |
|
||||||
*/ |
|
||||||
void switchCamera() { |
|
||||||
if (camera2Helper != null) { |
|
||||||
camera2Helper.switchCamera(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { |
|
||||||
Log.e(TAG, "onSurfaceTextureAvailable..."); |
|
||||||
startPreview(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { |
|
||||||
Log.e(TAG, "onSurfaceTextureDestroyed..."); |
|
||||||
stopPreview(); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onSurfaceTextureUpdated(SurfaceTexture surface) { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onPreviewFrame(byte[] y, byte[] u, byte[] v) { |
|
||||||
if (isPushing && mLiveUtil != null) { |
|
||||||
mLiveUtil.pushVideoData(y, u, v); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onCameraOpened(Size previewSize, int displayOrientation) { |
|
||||||
Log.e(TAG, "onCameraOpened previewSize=" + previewSize.toString()); |
|
||||||
if (mLiveUtil != null && mVideoParam != null) { |
|
||||||
mLiveUtil.setVideoParams(previewSize.getWidth(), previewSize.getHeight(), |
|
||||||
mVideoParam.getBitRate(), mVideoParam.getFrameRate()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onCameraClosed() { |
|
||||||
Log.e(TAG, "onCameraClosed"); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onCameraError(Exception e) { |
|
||||||
Log.e(TAG, "onCameraError=" + e.toString()); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
Loading…
Reference in new issue