From c1fb401774f53b88fdc1843edc19ad74fcb8b699 Mon Sep 17 00:00:00 2001 From: frank <839789740@qq.com> Date: Mon, 12 Feb 2018 16:29:54 +0800 Subject: [PATCH] =?UTF-8?q?FFmpeg=E9=9F=B3=E8=A7=86=E9=A2=91=E8=A7=A3?= =?UTF-8?q?=E7=A0=81=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/CMakeLists.txt | 4 +- app/src/main/AndroidManifest.xml | 16 +- app/src/main/cpp/AVpacket_queue.c | 63 +++ app/src/main/cpp/AVpacket_queue.h | 25 + app/src/main/cpp/media_player.c | 505 ++++++++++++++++++ .../java/com/frank/ffmpeg/MediaPlayer.java | 45 ++ .../frank/ffmpeg/activity/MainActivity.java | 17 +- .../ffmpeg/activity/MediaPlayerActivity.java | 81 +++ .../ffmpeg/activity/VideoPlayerActivity.java | 2 +- .../main/res/layout/activity_audio_handle.xml | 2 +- app/src/main/res/layout/activity_main.xml | 26 +- .../main/res/layout/activity_media_player.xml | 33 ++ app/src/main/res/values/strings.xml | 4 +- 13 files changed, 805 insertions(+), 18 deletions(-) create mode 100644 app/src/main/cpp/AVpacket_queue.c create mode 100644 app/src/main/cpp/AVpacket_queue.h create mode 100644 app/src/main/cpp/media_player.c create mode 100644 app/src/main/java/com/frank/ffmpeg/MediaPlayer.java create mode 100644 app/src/main/java/com/frank/ffmpeg/activity/MediaPlayerActivity.java create mode 100644 app/src/main/res/layout/activity_media_player.xml diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3bd28bd..9bece44 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -26,7 +26,9 @@ add_library( # Sets the name of the library. src/main/cpp/audio_player.c src/main/cpp/openSL_audio_player.c src/main/cpp/video_player.c - src/main/cpp/ffmpeg_pusher.cpp) + src/main/cpp/ffmpeg_pusher.cpp + src/main/cpp/AVpacket_queue.c + src/main/cpp/media_player.c) add_library( ffmpeg SHARED diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 352a7bb..decfda7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,19 +27,21 @@ - + - + - + - + + + + + - - - \ No newline at end of file diff --git a/app/src/main/cpp/AVpacket_queue.c b/app/src/main/cpp/AVpacket_queue.c new file mode 100644 index 0000000..3ed1f0e --- /dev/null +++ b/app/src/main/cpp/AVpacket_queue.c @@ -0,0 +1,63 @@ +// +// Created by frank on 2018/2/3. +// +#include "AVpacket_queue.h" +#include +#include + + +AVPacketQueue* queue_init(int size){ + AVPacketQueue* queue = malloc(sizeof(AVPacketQueue)); + queue->size = size; + queue->next_to_read = 0; + queue->next_to_write = 0; + int i; + queue->packets = malloc(sizeof(*queue->packets) * size); + for(i = 0; i < size; i++){ + queue->packets[i] = malloc(sizeof(AVPacket)); + } + return queue; +} + +void queue_free(AVPacketQueue *queue){ + int i; + for(i=0; isize; i++){ + free(queue->packets[i]); + } + free(queue->packets); + free(queue); +} + +int queue_next(AVPacketQueue* queue, int current){ + return (current+1) % queue->size; +} + +void* queue_push(AVPacketQueue* queue, pthread_mutex_t* mutex, pthread_cond_t* cond){ + int current = queue->next_to_write; + int next_to_write; + for(;;){ + next_to_write = queue_next(queue, current); + //写的不等于读的,跳出循环 + if(next_to_write != queue->next_to_read){ + break; + } + pthread_cond_wait(cond, mutex); + } + queue->next_to_write = next_to_write; + pthread_cond_broadcast(cond); + return queue->packets[current]; +} + +void* queue_pop(AVPacketQueue* queue, pthread_mutex_t* mutex, pthread_cond_t* cond){ + int current = queue->next_to_read; + for(;;){ + //写的不等于读的,跳出循环 + if(queue->next_to_write != queue->next_to_read){ + break; + } + pthread_cond_wait(cond, mutex); + } + queue->next_to_read = queue_next(queue, current); + pthread_cond_broadcast(cond); + return queue->packets[current]; +} \ No newline at end of file diff --git a/app/src/main/cpp/AVpacket_queue.h b/app/src/main/cpp/AVpacket_queue.h new file mode 100644 index 0000000..3e899f8 --- /dev/null +++ b/app/src/main/cpp/AVpacket_queue.h @@ -0,0 +1,25 @@ +// +// Created by frank on 2018/2/3. +// + +#ifndef VIDEOPLAYER_AVPACKET_QUEUE_H +#define VIDEOPLAYER_AVPACKET_QUEUE_H +#include + +typedef struct AVPacketQueue{ + //队列大小 + int size; + //指针数组 + void ** packets; + //下一个写入的packet + int next_to_write; + //下一个读取的packet + int next_to_read; +}AVPacketQueue; + +AVPacketQueue* queue_init(int size); +void queue_free(AVPacketQueue *queue); +void* queue_push(AVPacketQueue* queue, pthread_mutex_t* mutex, pthread_cond_t* cond); +void* queue_pop(AVPacketQueue* queue, pthread_mutex_t* mutex, pthread_cond_t* cond); + +#endif //VIDEOPLAYER_AVPACKET_QUEUE_H \ No newline at end of file diff --git a/app/src/main/cpp/media_player.c b/app/src/main/cpp/media_player.c new file mode 100644 index 0000000..ea974e4 --- /dev/null +++ b/app/src/main/cpp/media_player.c @@ -0,0 +1,505 @@ +// +// Created by frank on 2018/2/3. +// +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +#include "libswscale/swscale.h" +#include "libswresample/swresample.h" +#include "AVpacket_queue.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "MediaPlayer" +#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__); + +#define MAX_AUDIO_FRAME_SIZE 48000 * 4 +#define PACKET_SIZE 50 +#define MIN_SLEEP_TIME_US 1000ll +#define AUDIO_TIME_ADJUST_US -200000ll + +typedef struct MediaPlayer{ + AVFormatContext* format_context; + int video_stream_index; + int audio_stream_index; + AVCodecContext* video_codec_context; + AVCodecContext* audio_codec_context; + AVCodec* video_codec; + AVCodec* audio_codec; + ANativeWindow* native_window; + uint8_t* buffer; + AVFrame* yuv_frame; + AVFrame* rgba_frame; + int video_width; + int video_height; + SwrContext* swrContext; + int out_channel_nb; + int out_sample_rate; + enum AVSampleFormat out_sample_fmt; + jobject audio_track; + jmethodID audio_track_write_mid; + uint8_t* audio_buffer; + AVFrame* audio_frame; + AVPacketQueue* packets[2]; + pthread_mutex_t mutex; + pthread_cond_t cond; + int64_t start_time; + int64_t audio_clock; + pthread_t write_thread; + pthread_t video_thread; + pthread_t audio_thread; +}MediaPlayer; + +typedef struct Decoder{ + MediaPlayer* player; + int stream_index; +}Decoder; + +JavaVM* javaVM; +MediaPlayer* player; + +jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){ + javaVM = vm; + return JNI_VERSION_1_6; +} + +//初始化输入格式上下文 +int init_input_format_context(MediaPlayer* player, const char* file_name){ + //注册所有组件 + av_register_all(); + //分配上下文 + player->format_context = avformat_alloc_context(); + //打开视频文件 + if(avformat_open_input(&player->format_context, file_name, NULL, NULL)!=0) { + LOGE("Couldn't open file:%s\n", file_name); + return -1; + } + //检索多媒体流信息 + if(avformat_find_stream_info(player->format_context, NULL)<0) { + LOGE("Couldn't find stream information."); + return -1; + } + //寻找音视频流索引位置 + int i; + player->video_stream_index = -1; + player->audio_stream_index = -1; + for (i = 0; i < player->format_context->nb_streams; i++) { + if (player->format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO + && player->video_stream_index < 0) { + player->video_stream_index = i; + } else if (player->format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO + && player->audio_stream_index < 0) { + player->audio_stream_index = i; + } + } + if(player->video_stream_index==-1) { + LOGE("couldn't find a video stream."); + return -1; + } + if(player->audio_stream_index==-1) { + LOGE("couldn't find a audio stream."); + return -1; + } + LOGI("video_stream_index=%d", player->video_stream_index); + LOGI("audio_stream_index=%d", player->audio_stream_index); + return 0; +} + +//打开音视频解码器 +int init_condec_context(MediaPlayer* player){ + //获取codec上下文指针 + player->video_codec_context = player->format_context->streams[player->video_stream_index]->codec; + //寻找视频流的解码器 + player->video_codec = avcodec_find_decoder(player->video_codec_context->codec_id); + if(player->video_codec == NULL) { + LOGE("couldn't find video Codec."); + return -1; + } + if(avcodec_open2(player->video_codec_context, player->video_codec, NULL) < 0) { + LOGE("Couldn't open video codec."); + return -1; + } + player->audio_codec_context = player->format_context->streams[player->audio_stream_index]->codec; + player->audio_codec = avcodec_find_decoder(player->audio_codec_context->codec_id); + if( player->audio_codec == NULL) { + LOGE("couldn't find audio Codec."); + return -1; + } + if(avcodec_open2(player->audio_codec_context, player->audio_codec, NULL) < 0) { + LOGE("Couldn't open audio codec."); + return -1; + } + // 获取视频宽高 + player->video_width = player->video_codec_context->width; + player->video_height = player->video_codec_context->height; + return 0; +} + +//视频解码 +void video_player_prepare(MediaPlayer* player, JNIEnv* env, jobject surface){ + // 获取native window + player->native_window = ANativeWindow_fromSurface(env, surface); +} + +//获取当前播放时间 +int64_t get_play_time(MediaPlayer* player){ + return (int64_t)(av_gettime() - player->start_time); +} + +/** + * 延迟等待,音视频同步 + */ +void player_wait_for_frame(MediaPlayer *player, int64_t stream_time) { + pthread_mutex_lock(&player->mutex); + for(;;){ + int64_t current_video_time = get_play_time(player); + int64_t sleep_time = stream_time - current_video_time; + if (sleep_time < -300000ll) { + // 300 ms late + int64_t new_value = player->start_time - sleep_time; + player->start_time = new_value; + pthread_cond_broadcast(&player->cond); + } + + if (sleep_time <= MIN_SLEEP_TIME_US) { + // We do not need to wait if time is slower then minimal sleep time + break; + } + + if (sleep_time > 500000ll) { + // if sleep time is bigger then 500ms just sleep this 500ms + // and check everything again + sleep_time = 500000ll; + } + //等待指定时长 + pthread_cond_timeout_np(&player->cond, &player->mutex, + (unsigned int) (sleep_time / 1000ll)); + } + pthread_mutex_unlock(&player->mutex); +} + +//视频解码 +int decode_video(MediaPlayer* player, AVPacket* packet){ + // 设置native window的buffer大小,可自动拉伸 + ANativeWindow_setBuffersGeometry(player->native_window, player->video_width, + player->video_height, WINDOW_FORMAT_RGBA_8888); + + ANativeWindow_Buffer windowBuffer; + //申请内存 + player->yuv_frame = av_frame_alloc(); + player->rgba_frame = av_frame_alloc(); + if(player->rgba_frame == NULL || player->yuv_frame == NULL) { + LOGE("Couldn't allocate video frame."); + return -1; + } + + // buffer中数据用于渲染,且格式为RGBA + int numBytes=av_image_get_buffer_size(AV_PIX_FMT_RGBA, player->video_width, player->video_height, 1); + + player->buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); + av_image_fill_arrays(player->rgba_frame->data, player->rgba_frame->linesize, player->buffer, AV_PIX_FMT_RGBA, + player->video_width, player->video_height, 1); + + // 由于解码出来的帧格式不是RGBA的,在渲染之前需要进行格式转换 + struct SwsContext *sws_ctx = sws_getContext( + player->video_width, + player->video_height, + player->video_codec_context->pix_fmt, + player->video_width, + player->video_height, + AV_PIX_FMT_RGBA, + SWS_BILINEAR, + NULL, + NULL, + NULL); + + int frameFinished; + //对该帧进行解码 + int ret = avcodec_decode_video2(player->video_codec_context, player->yuv_frame, &frameFinished, packet); + if(ret < 0){ + LOGE("avcodec_decode_video2 error..."); + return -1; + } + if (frameFinished) { + // lock native window + ANativeWindow_lock(player->native_window, &windowBuffer, 0); + // 格式转换 + sws_scale(sws_ctx, (uint8_t const * const *)player->yuv_frame->data, + player->yuv_frame->linesize, 0, player->video_height, + player->rgba_frame->data, player->rgba_frame->linesize); + // 获取stride + uint8_t * dst = windowBuffer.bits; + int dstStride = windowBuffer.stride * 4; + uint8_t * src = player->rgba_frame->data[0]; + int srcStride = player->rgba_frame->linesize[0]; + // 由于window的stride和帧的stride不同,因此需要逐行复制 + int h; + for (h = 0; h < player->video_height; h++) { + memcpy(dst + h * dstStride, src + h * srcStride, (size_t) srcStride); + } + + //计算延迟 + int64_t pts = av_frame_get_best_effort_timestamp(player->yuv_frame); + AVStream *stream = player->format_context->streams[player->video_stream_index]; + //转换(不同时间基时间转换) + int64_t time = av_rescale_q(pts, stream->time_base, AV_TIME_BASE_Q); + //音视频帧同步 + player_wait_for_frame(player, time); + + ANativeWindow_unlockAndPost(player->native_window); + } +// //延迟等待 +// usleep(1000 * 16); + return 0; +} + +//音频解码初始化 +void audio_decoder_prepare(MediaPlayer* player) { + //frame->16bit 44100 PCM 统一音频采样格式与采样率 + player->swrContext = swr_alloc(); + + //输入的采样格式 + enum AVSampleFormat in_sample_fmt = player->audio_codec_context->sample_fmt; + //输出采样格式16bit PCM + player->out_sample_fmt = AV_SAMPLE_FMT_S16; + //输入采样率 + int in_sample_rate = player->audio_codec_context->sample_rate; + //输出采样率 + player->out_sample_rate = in_sample_rate; + //声道布局(2个声道,默认立体声stereo) + uint64_t in_ch_layout = player->audio_codec_context->channel_layout; + //输出的声道布局(立体声) + uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO; + + swr_alloc_set_opts(player->swrContext, + out_ch_layout, player->out_sample_fmt, player->out_sample_rate, + in_ch_layout, in_sample_fmt, in_sample_rate, + 0, NULL); + swr_init(player->swrContext); + //输出的声道个数 + player->out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout); +} + +//音频播放器 +void audio_player_prepare(MediaPlayer* player, JNIEnv* env, jclass jthiz){ + jclass player_class = (*env)->GetObjectClass(env,jthiz); + if(!player_class){ + LOGE("player_class not found..."); + } + //AudioTrack对象 + jmethodID audio_track_method = (*env)->GetMethodID( + env,player_class,"createAudioTrack","(II)Landroid/media/AudioTrack;"); + if(!audio_track_method){ + LOGE("audio_track_method not found..."); + } + jobject audio_track = (*env)->CallObjectMethod( + env,jthiz,audio_track_method, player->out_sample_rate, player->out_channel_nb); + + //调用play方法 + jclass audio_track_class = (*env)->GetObjectClass(env, audio_track); + jmethodID audio_track_play_mid = (*env)->GetMethodID(env,audio_track_class,"play","()V"); + (*env)->CallVoidMethod(env, audio_track, audio_track_play_mid); + + player->audio_track = (*env)->NewGlobalRef(env, audio_track); + //获取write()方法 + player->audio_track_write_mid = (*env)->GetMethodID(env,audio_track_class,"write","([BII)I"); + + //16bit 44100 PCM 数据 + player->audio_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE); + //解压缩数据 + player->audio_frame = av_frame_alloc(); +} + +//音频解码 +int decode_audio(MediaPlayer* player, AVPacket* packet){ + int got_frame = 0, ret; + //解码 + ret = avcodec_decode_audio4(player->audio_codec_context, player->audio_frame, &got_frame, packet); + if(ret < 0){ + LOGE("avcodec_decode_audio4 error..."); + return -1; + } + //解码一帧成功 + if(got_frame > 0){ + //音频格式转换 + swr_convert(player->swrContext, &player->audio_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)player->audio_frame->data, player->audio_frame->nb_samples); + int out_buffer_size = av_samples_get_buffer_size(NULL, player->out_channel_nb, + player->audio_frame->nb_samples, player->out_sample_fmt, 1); + + //音视频帧同步 + int64_t pts = packet->pts; + if (pts != AV_NOPTS_VALUE) { + AVStream *stream = player->format_context->streams[player->audio_stream_index]; + player->audio_clock = av_rescale_q(pts, stream->time_base, AV_TIME_BASE_Q); + player_wait_for_frame(player, player->audio_clock + AUDIO_TIME_ADJUST_US); + } + + JNIEnv * env; + (*javaVM)->AttachCurrentThread(javaVM, &env, NULL); + jbyteArray audio_sample_array = (*env)->NewByteArray(env,out_buffer_size); + jbyte* sample_byte_array = (*env)->GetByteArrayElements(env,audio_sample_array,NULL); + //拷贝缓冲数据 + memcpy(sample_byte_array, player->audio_buffer, (size_t) out_buffer_size); + //释放数组 + (*env)->ReleaseByteArrayElements(env,audio_sample_array,sample_byte_array,0); + //调用AudioTrack的write方法进行播放 + (*env)->CallIntMethod(env, player->audio_track, player->audio_track_write_mid, + audio_sample_array,0,out_buffer_size); + //释放局部引用 + (*env)->DeleteLocalRef(env,audio_sample_array); +// usleep(1000 * 16); + } + (*javaVM)->DetachCurrentThread(javaVM); + return 0; +} + +//初始化队列 +void init_queue(MediaPlayer* player, int size){ + int i; + for (i = 0; i < 2; ++i) { + AVPacketQueue* queue = queue_init(size); + player->packets[i] = queue; + } +} + +//释放队列 +void delete_queue(MediaPlayer* player){ + int i; + for (i = 0; i < 2; ++i) { + queue_free(player->packets[i]); + } +} + +//读取AVPacket线程(生产者) +void* write_packet_to_queue(void* arg){ + MediaPlayer* player = (MediaPlayer*)arg; + AVPacket packet, *pkt = &packet; + int ret; + for(;;){ + ret = av_read_frame(player->format_context, pkt); + if(ret < 0){ + break; + } + if(pkt->stream_index == player->video_stream_index || pkt->stream_index == player->audio_stream_index){ + //根据AVPacket->stream_index获取对应的队列 + AVPacketQueue *queue = player->packets[pkt->stream_index]; + pthread_mutex_lock(&player->mutex); + AVPacket* data = queue_push(queue, &player->mutex, &player->cond); + pthread_mutex_unlock(&player->mutex); + //拷贝(间接赋值,拷贝结构体数据) + *data = packet; + } + } +} + +//音视频解码线程(消费者) +void* decode_func(void* arg){ + Decoder *decoder_data = (Decoder*)arg; + MediaPlayer *player = decoder_data->player; + int stream_index = decoder_data->stream_index; + //根据stream_index获取对应的AVPacket队列 + AVPacketQueue *queue = player->packets[stream_index]; + int ret = 0; + int video_frame_count = 0, audio_frame_count = 0; + for(;;) { + pthread_mutex_lock(&player->mutex); + AVPacket *packet = (AVPacket*)queue_pop(queue, &player->mutex, &player->cond); + pthread_mutex_unlock(&player->mutex); + + if(stream_index == player->video_stream_index) {//视频流 + ret = decode_video(player, packet); + LOGI("decode video stream = %d", video_frame_count++); + } else if(stream_index == player->audio_stream_index) {//音频流 + ret = decode_audio(player, packet); + LOGI("decode audio stream = %d", audio_frame_count++); + } + av_packet_unref(packet); + if(ret < 0){ + break; + } + } +} + +JNIEXPORT jint JNICALL Java_com_frank_ffmpeg_MediaPlayer_setup + (JNIEnv * env, jclass clazz, jstring filePath, jobject surface){ + + const char *file_name = (*env)->GetStringUTFChars(env, filePath, JNI_FALSE); + int ret; + player = malloc(sizeof(MediaPlayer)); + if(player == NULL){ + return -1; + } + //初始化输入格式上下文 + ret = init_input_format_context(player, file_name); + if(ret < 0){ + return ret; + } + //初始化音视频解码器 + ret = init_condec_context(player); + if(ret < 0){ + return ret; + } + //初始化视频surface + video_player_prepare( player, env, surface); + //初始化音频相关参数 + audio_decoder_prepare(player); + //初始化音频播放器 + audio_player_prepare(player, env, clazz); + //初始化音视频packet队列 + init_queue(player, PACKET_SIZE); + + return 0; +} + +JNIEXPORT jint JNICALL Java_com_frank_ffmpeg_MediaPlayer_play + (JNIEnv * env, jclass clazz){ + pthread_mutex_init(&player->mutex, NULL); + pthread_cond_init(&player->cond, NULL); + + //生产者线程 + pthread_create(&player->write_thread, NULL, write_packet_to_queue, (void*)player); + sleep(1); + player->start_time = 0; + + //消费者线程 + Decoder data1 = {player, player->video_stream_index}, *decoder_data1 = &data1; + pthread_create(&player->video_thread, NULL, decode_func, (void*)decoder_data1); + + Decoder data2 = {player, player->audio_stream_index}, *decoder_data2 = &data2; + pthread_create(&player->audio_thread,NULL,decode_func,(void*)decoder_data2); + + + pthread_join(player->write_thread, NULL); + pthread_join(player->video_thread, NULL); + pthread_join(player->audio_thread, NULL); + + return 0; +} + +JNIEXPORT void JNICALL Java_com_frank_ffmpeg_MediaPlayer_release + (JNIEnv * env, jclass clazz){ + //释放内存以及关闭文件 + free(player->audio_track); + free(player->audio_track_write_mid); + av_free(player->buffer); + av_free(player->rgba_frame); + av_free(player->yuv_frame); + av_free(player->audio_buffer); + av_free(player->audio_frame); + avcodec_close(player->video_codec_context); + avcodec_close(player->audio_codec_context); + avformat_close_input(&player->format_context); + ANativeWindow_release(player->native_window); + delete_queue(player); + pthread_cond_destroy(&player->cond); + pthread_mutex_destroy(&player->mutex); + free(player); + (*javaVM)->DestroyJavaVM(javaVM); +} diff --git a/app/src/main/java/com/frank/ffmpeg/MediaPlayer.java b/app/src/main/java/com/frank/ffmpeg/MediaPlayer.java new file mode 100644 index 0000000..5380df5 --- /dev/null +++ b/app/src/main/java/com/frank/ffmpeg/MediaPlayer.java @@ -0,0 +1,45 @@ +package com.frank.ffmpeg; + +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; + +/** + * 音视频播放器 + * Created by frank on 2018/2/12. + */ + +public class MediaPlayer { + static { + System.loadLibrary("media-handle"); + } + + public native int setup(String filePath, Object surface); + public native int play(); + public native void release(); + + + /** + * 创建一个AudioTrack对象 + * @param sampleRate 采样率 + * @param channels 声道布局 + * @return AudioTrack + */ + public AudioTrack createAudioTrack(int sampleRate, int channels){ + int audioFormat = AudioFormat.ENCODING_PCM_16BIT; + int channelConfig; + if(channels == 1){ + channelConfig = android.media.AudioFormat.CHANNEL_OUT_MONO; + }else if(channels == 2){ + channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO; + }else{ + channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO; + } + + int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); + + return new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, + bufferSizeInBytes, AudioTrack.MODE_STREAM); + } + +} diff --git a/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java index 05811c2..91e6b35 100644 --- a/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java +++ b/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java @@ -4,6 +4,8 @@ import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; +import android.widget.Toast; + import com.frank.ffmpeg.R; /** @@ -22,9 +24,11 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe private void initView() { findViewById(R.id.btn_audio).setOnClickListener(this); - findViewById(R.id.btn_media).setOnClickListener(this); findViewById(R.id.btn_video).setOnClickListener(this); + findViewById(R.id.btn_media).setOnClickListener(this); + findViewById(R.id.btn_play).setOnClickListener(this); findViewById(R.id.btn_push).setOnClickListener(this); + findViewById(R.id.btn_live).setOnClickListener(this); } @Override @@ -33,15 +37,22 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe case R.id.btn_audio://音频处理 startActivity(new Intent(MainActivity.this, AudioHandleActivity.class)); break; + case R.id.btn_video://视频处理 + startActivity(new Intent(MainActivity.this, VideoHandleActivity.class)); + break; case R.id.btn_media://音视频处理 startActivity(new Intent(MainActivity.this, MediaHandleActivity.class)); break; - case R.id.btn_video://视频处理 - startActivity(new Intent(MainActivity.this, VideoHandleActivity.class)); + case R.id.btn_play://音视频播放 + startActivity(new Intent(MainActivity.this, MediaPlayerActivity.class)); break; case R.id.btn_push://FFmpeg推流 startActivity(new Intent(MainActivity.this, PushActivity.class)); break; + case R.id.btn_live://实时推流直播:AAC音频编码、H264视频编码、RTMP推流 + Toast.makeText(MainActivity.this, "暂时没该功能...", Toast.LENGTH_SHORT).show(); +// startActivity(new Intent(MainActivity.this, PushActivity.class)); + break; default: break; } diff --git a/app/src/main/java/com/frank/ffmpeg/activity/MediaPlayerActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/MediaPlayerActivity.java new file mode 100644 index 0000000..77b33c4 --- /dev/null +++ b/app/src/main/java/com/frank/ffmpeg/activity/MediaPlayerActivity.java @@ -0,0 +1,81 @@ +package com.frank.ffmpeg.activity; + +import android.os.Environment; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import com.frank.ffmpeg.MediaPlayer; +import com.frank.ffmpeg.R; +import java.io.File; + +/** + * 音视频解码播放 + * Created by frank on 2018/2/12. + */ + +public class MediaPlayerActivity extends AppCompatActivity implements SurfaceHolder.Callback { + private static final String TAG = MediaPlayerActivity.class.getSimpleName(); + SurfaceHolder surfaceHolder; + private final static String PATH = Environment.getExternalStorageDirectory().getPath() + File.separator; + private String filePath = PATH + "hello.mp4"; + private MediaPlayer mediaPlayer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_media_player); + initView(); + initPlayer(); + } + + private void initView(){ + SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface_media); + surfaceHolder = surfaceView.getHolder(); + surfaceHolder.addCallback(this); + +// Button btn_slow = (Button) findViewById(R.id.btn_play_slow); +// Button btn_fast = (Button) findViewById(R.id.btn_play_fast); + } + + private void initPlayer(){ + mediaPlayer = new MediaPlayer(); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.i(TAG, "surfaceCreated"); + new Thread(new Runnable() { + @Override + public void run() { + int result = mediaPlayer.setup(filePath, surfaceHolder.getSurface()); + if(result < 0){ + Log.e(TAG, "mediaPlayer-->setup"); + return; + } + mediaPlayer.play(); + } + }).start(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.i(TAG, "surfaceChanged"); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.i(TAG, "surfaceDestroyed"); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if(mediaPlayer != null){ + mediaPlayer.release(); + mediaPlayer = null; + } + } + +} diff --git a/app/src/main/java/com/frank/ffmpeg/activity/VideoPlayerActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/VideoPlayerActivity.java index 8a0a41d..a6b4c65 100644 --- a/app/src/main/java/com/frank/ffmpeg/activity/VideoPlayerActivity.java +++ b/app/src/main/java/com/frank/ffmpeg/activity/VideoPlayerActivity.java @@ -19,7 +19,7 @@ import java.io.File; * Created by frank on 2018/2/1. */ public class VideoPlayerActivity extends AppCompatActivity implements SurfaceHolder.Callback { - private static final String TAG = MainActivity.class.getSimpleName(); + private static final String TAG = VideoPlayerActivity.class.getSimpleName(); SurfaceHolder surfaceHolder; private final static String PATH = Environment.getExternalStorageDirectory().getPath() + File.separator; private String filePath = PATH + "hello.mp4"; diff --git a/app/src/main/res/layout/activity_audio_handle.xml b/app/src/main/res/layout/activity_audio_handle.xml index 68f42d2..26e0845 100644 --- a/app/src/main/res/layout/activity_audio_handle.xml +++ b/app/src/main/res/layout/activity_audio_handle.xml @@ -53,7 +53,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/audio_play" - android:layout_below="@+id/btn_concat" + android:layout_below="@+id/btn_mix" android:layout_marginTop="10dp" android:layout_centerHorizontal="true"/> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3ab3ac2..5b72dbf 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,6 +11,15 @@ android:layout_centerHorizontal="true" android:text="@string/audio_handle"/> +