项目结构改了亿点点
app -> a_ffmpeg
新增了 a_jni,用来存放idea 默认的 jni结构
1, 读取音频信息
先提取一个mp3
SENRSL:1-macos版ffmpeg命令 senrsl$ ./ffmpeg -i VID_20201211_155754.mp4 -f mp3 -ar 16000 audio03.mp3
ffmpeg version 4.3.1-tessus https://evermeet.cx/ffmpeg/ Copyright (c) 2000-2020 the FFmpeg developers
built with Apple clang version 11.0.0 (clang-1100.0.33.17)
configuration: --cc=/usr/bin/clang --prefix=/opt/ffmpeg --extra-version=tessus --enable-avisynth --enable-fontconfig --enable-gpl --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libfreetype --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libmysofa --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopus --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvmaf --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-version3 --pkg-config-flags=--static --disable-ffplay
libavutil 56. 51.100 / 56. 51.100
libavcodec 58. 91.100 / 58. 91.100
libavformat 58. 45.100 / 58. 45.100
libavdevice 58. 10.100 / 58. 10.100
libavfilter 7. 85.100 / 7. 85.100
libswscale 5. 7.100 / 5. 7.100
libswresample 3. 7.100 / 3. 7.100
libpostproc 55. 7.100 / 55. 7.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'VID_20201211_155754.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: isommp42
creation_time : 2020-12-11T07:57:54.000000Z
com.android.version: 11
com.android.capture.fps: 30.000000
Duration: 00:00:15.92, start: 0.000000, bitrate: 7063 kb/s
Stream #0:0(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 192 kb/s (default)
Metadata:
creation_time : 2020-12-11T07:57:54.000000Z
handler_name : SoundHandle
Stream #0:1(eng): Video: hevc (Main) (hvc1 / 0x31637668), yuvj420p(pc, smpte170m/bt470bg/smpte170m), 1920x1080, 6865 kb/s, SAR 1:1 DAR 16:9, 57.84 fps, 60 tbr, 90k tbn, 90k tbc (default)
Metadata:
rotate : 90
creation_time : 2020-12-11T07:57:54.000000Z
handler_name : VideoHandle
Side data:
displaymatrix: rotation of -90.00 degrees
Stream mapping:
Stream #0:0 -> #0:0 (aac (native) -> mp3 (libmp3lame))
Press [q] to stop, [?] for help
Output #0, mp3, to 'audio03.mp3':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: isommp42
com.android.capture.fps: 30.000000
com.android.version: 11
TSSE : Lavf58.45.100
Stream #0:0(eng): Audio: mp3 (libmp3lame), 16000 Hz, stereo, fltp (default)
Metadata:
creation_time : 2020-12-11T07:57:54.000000Z
handler_name : SoundHandle
encoder : Lavc58.91.100 libmp3lame
size= 94kB time=00:00:15.93 bitrate= 48.3kbits/s speed=49.5x
video:0kB audio:94kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.413956%
SENRSL:1-macos版ffmpeg命令 senrsl$ cp audio03.mp3 ../3-使用Android版ffmpeg/
SENRSL:1-macos版ffmpeg命令 senrsl$
然后把这个 mp3 push 到手机里。。。。
幸好 FFmpeg 不做死
然后发现,Intellij idea 竟然没有 C/C++ 插件。。。。stackoverflow.com/questions/55334133/c-c-plugin-for-intellij-idea-community-edition
一款开发工具竟然不支持C/C++.....
新增 play_audio.cpp,配置同前,这样不用改CMake.txt,打出来的都在 ffutils.so里
SENRSL:main senrsl$ cat cpp/play-audio.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
//include不要放在这儿,要放到 extern C 里面去,不然各种找不到
#define LOG_TAG "TEST_JNI"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
extern "C"{
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
JNIEXPORT jstring JNICALL
Java_dc_test_ffmpeg_Test03PlayAudioActivity_printAudioInfo(JNIEnv *env, jobject instance,jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "hello native log");
LOGE("待处理地址:%s %s","aaaaa :",url);
//1,av_register_all():的作用是初始化所有组件,只有调用了该函数,才能使用复用器和编解码器
av_register_all();
AVFormatContext *avFormatContext = NULL;
int audio_stream_idx;
AVStream *audio_stream;
//2,avformat_open_input()/avformat_close_input()函数会读文件头,对 mp4 文件而言,它会解析所有的 box。但它只把读到的结果保存在对应的数据结构下。这个时候,AVStream 中的很多字段都是空白的。
int open_res = avformat_open_input(&avFormatContext, url, NULL, NULL);
if (open_res != 0) {
LOGE("Can't open file: %s", av_err2str(open_res));
return env->NewStringUTF("1111111111");
}
//3,获取文件信息
//读取一部分视音频数据并且获得一些相关的信息,会检测一些重要字段,如果是空白的,就设法填充它们。
// 因为我们解析文件头的时候,已经掌握了大量的信息,avformat_find_stream_info 就是通过这些信息来填充自己的成员,
// 当重要的成员都填充完毕后,该函数就返回了。这中情况下,该函数效率很高。但对于某些文件,单纯的从文件头中获取信息是不够的,
// 比如 video 的 pix_fmt 是需要调用 h264_decode_frame 才可以获取其pix_fmt的。
int find_stream_info_res = avformat_find_stream_info(avFormatContext, NULL);
if (find_stream_info_res < 0) {
LOGE("Find stream info error: %s", av_err2str(find_stream_info_res));
goto __avformat_close;
}
//4,获取采样率和通道
//av_find_best_stream:获取音视频及字幕的 stream_index , 以前没有这个函数时,我们一般都是写的 for 循环。
audio_stream_idx = av_find_best_stream(avFormatContext, AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_stream_idx < 0) {
LOGE("Find audio stream info error: %s", av_err2str(find_stream_info_res));
goto __avformat_close;
}
audio_stream = avFormatContext->streams[audio_stream_idx];
LOGE("采样率:%d",audio_stream->codecpar->sample_rate);
LOGE("通道数: %d", audio_stream->codecpar->channels);
LOGE("format: %d", audio_stream->codecpar->format);
LOGE("extradata_size: %d", audio_stream->codecpar->extradata_size);
__avformat_close:
avformat_close_input(&avFormatContext);
return env->NewStringUTF("22222222Intellij竟然不支持C++插件也没有。。。。");
}
}SENRSL:main senrsl$
相应的, 写一个Activity来执行它
SENRSL:main senrsl$ cat java/dc/test/ffmpeg/Test03PlayAudioActivity.kt
package dc.test.ffmpeg
import android.Manifest
import android.content.Context
import android.content.Intent
import dc.android.base.activity.BridgeActivity
import dc.android.libs.PermissionUtils
import dc.android.libs.permission.AbsPermissionCallback
import dc.common.Logger
/**
*
*
* @ClassName: Test03PlayAudioActivity
* @author senrsl
*
* @Package: dc.test.ffmpeg
* @CreateTime: 2020/12/14 11:00 上午
*/
class Test03PlayAudioActivity : BridgeActivity() {
init {
System.loadLibrary("ffutils")
}
external fun printAudioInfo(path: String): String
companion object {
@JvmStatic
fun start(context: Context) {
val starter = Intent(context, Test03PlayAudioActivity::class.java)
context.startActivity(starter)
}
}
override fun initData() {
super.initData()
PermissionUtils.with(this)
.permisson(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.callback(object : AbsPermissionCallback() {
override fun onResult(isAllGrant: Boolean, hasDenied: Boolean, hasRationale: Boolean) {
Logger.w(this@Test03PlayAudioActivity, "授权$isAllGrant")
//android 11 /mnt/sdcard/ 这个路径不好使了,不断做死的google。。。。
//val path = "/mnt/sdcard/SENRSL/audio03.mp3"
val path = "/sdcard/SENRSL/audio03.mp3"
//val file = File(path)
val result = printAudioInfo(path)
Logger.w(result)
}
}).request()
}
}SENRSL:main senrsl$
执行,便获取到了mp3的信息。。。。
2020-12-14 16:13:31.562 31189-31189/dc.test.ffmpeg D/CompatibilityChangeReporter: Compat change id reported: 147798919; UID 10145; state: DISABLED
2020-12-14 16:13:31.571 31189-31189/dc.test.ffmpeg W/TEST: 授权true
2020-12-14 16:13:31.571 31189-31189/dc.test.ffmpeg D/TEST_JNI: hello native log
2020-12-14 16:13:31.571 31189-31189/dc.test.ffmpeg E/TEST_JNI: 待处理地址:aaaaa : /sdcard/SENRSL/audio03.mp3
2020-12-14 16:13:31.584 31189-31189/dc.test.ffmpeg E/TEST_JNI: 采样率:16000
2020-12-14 16:13:31.584 31189-31189/dc.test.ffmpeg E/TEST_JNI: 通道数: 2
2020-12-14 16:13:31.584 31189-31189/dc.test.ffmpeg E/TEST_JNI: format: 8
2020-12-14 16:13:31.584 31189-31189/dc.test.ffmpeg E/TEST_JNI: extradata_size: 0
2020-12-14 16:13:31.585 31189-31189/dc.test.ffmpeg W/TEST: 22222222Intellij竟然不支持C++插件也没有。。。。
这么看的话,重点就是这几个函数
1,先使用 av_register_all 函数 注册编解码器等组件,做初始化操作;
2,读取文件头信息,(通过avformat库下的avformat_open_input);
3,读取流信息填充补齐文件头信息(通过avformat_find_stream_info()方法);
4,找到最佳打开流方式(通过av_find_best_stream方法找到最佳的视频及音频流通道以进行解码);
5,关闭文件头avformat_close_input,对应2打开;
这样的话,就可以 去解码音频视频来播放了。。。。
2,播放音频
播放音频步骤
上面12345肯定是有的,不过在4之后,就要开始解码器了
1,av_register_all()
av_register_all();
2,avformat_open_input()
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
3,avformat_find_stream_info()
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
4,av_find_best_stream()
int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags);
返回了一个int类型的index;
传入参数第二个类型是个枚举的type,看起来跟android调用相机一样,是想要视频还是照片通过它来判断;
enum AVMediaType { AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATA AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuous AVMEDIA_TYPE_SUBTITLE, AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse AVMEDIA_TYPE_NB };
枚举很明确。。。。
5,avcodec_find_decoder()
查找解码器
/** * Find a registered decoder with a matching codec ID. * * @param id AVCodecID of the requested decoder * @return A decoder if one was found, NULL otherwise. */ AVCodec *avcodec_find_decoder(enum AVCodecID id);
通过AVCodecID 查到到解码器,这个id 来自于pFormatContext。。。。
6,avcodec_open2()
打开解码器
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
返回0 表示成功
7,av_read_frame()
逐帧读取
/** * Return the next frame of a stream. * This function returns what is stored in the file, and does not validate * that what is there are valid frames for the decoder. It will split what is * stored in the file into frames and return one for each call. It will not * omit invalid data between valid frames so as to give the decoder the maximum * information possible for decoding. * * On success, the returned packet is reference-counted (pkt->buf is set) and * valid indefinitely. The packet must be freed with av_packet_unref() when * it is no longer needed. For video, the packet contains exactly one frame. * For audio, it contains an integer number of frames if each frame has * a known fixed size (e.g. PCM or ADPCM data). If the audio frames have * a variable size (e.g. MPEG audio), then it contains one frame. * * pkt->pts, pkt->dts and pkt->duration are always set to correct * values in AVStream.time_base units (and guessed if the format cannot * provide them). pkt->pts can be AV_NOPTS_VALUE if the video format * has B-frames, so it is better to rely on pkt->dts if you do not * decompress the payload. * * @return 0 if OK, < 0 on error or end of file. On error, pkt will be blank * (as if it came from av_packet_alloc()). * * @note pkt will be initialized, so it may be uninitialized, but it must not * contain data that needs to be freed. */ int av_read_frame(AVFormatContext *s, AVPacket *pkt);
似曾相识
8,avcodec_send_packet()
把数据发送给解码器
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
9,avcodec_receive_frame()
从解码器获取解码后数据
发送,接收
/** * Return decoded output data from a decoder. * * @param avctx codec context * @param frame This will be set to a reference-counted video or audio * frame (depending on the decoder type) allocated by the * decoder. Note that the function will always call * av_frame_unref(frame) before doing anything else. * * @return * 0: success, a frame was returned * AVERROR(EAGAIN): output is not available in this state - user must try * to send new input * AVERROR_EOF: the decoder has been fully flushed, and there will be * no more output frames * AVERROR(EINVAL): codec not opened, or it is an encoder * AVERROR_INPUT_CHANGED: current decoded frame has changed parameters * with respect to first decoded frame. Applicable * when flag AV_CODEC_FLAG_DROPCHANGED is set. * other negative values: legitimate decoding errors */ int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
拿到了解码后数据,一帧AVFrame格式,还得需要转换一下,把AVFrame转成PCM裸流
10,AVFrame转裸流PCM
用到了libswresample.h
/** Convert audio. * * in and in_count can be set to 0 to flush the last few samples out at the * end. * * If more input is provided than output space, then the input will be buffered. * You can avoid this buffering by using swr_get_out_samples() to retrieve an * upper bound on the required number of output samples for the given number of * input samples. Conversion will run directly without copying whenever possible. * * @param s allocated Swr context, with parameters set * @param out output buffers, only the first one need be set in case of packed audio * @param out_count amount of space available for output in samples per channel * @param in input buffers, only the first one need to be set in case of packed audio * @param in_count number of input samples available in one channel * * @return number of samples output per channel, negative value on error */ int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in , int in_count);
转换
11,这样就拿到解码后的原始pcm流数据了,交给播放器去播放
12,回收
感觉这个逻辑有问题,先复制粘贴跑跑,结果发现 竟然一直崩溃。。。。
花了好久终于跑起来。。。。
一份能跑的play_audio2.cpp
#include <jni.h> #include <string> #include <android/log.h> //ffmpeg 是c写的,要用c的include extern "C"{ #include "libavformat/avformat.h" #include "libswresample/swresample.h" }; //using namespace std; #define TAG "TEST_JNI" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) #define AUDIO_SAMPLE_RATE 44100 //暂时用全局变量,后面再抽取优化 jmethodID jAudioTrackWriteMid; jobject audioTrack; /** * 创建 java 的 AudioTrack * @param env * @return */ jobject initAudioTrack(JNIEnv *env){ jclass jAudioTrackClass = env->FindClass("android/media/AudioTrack"); jmethodID jAudioTrackCMid = env->GetMethodID(jAudioTrackClass,"<init>","(IIIIII)V"); //构造 // public static final int STREAM_MUSIC = 3; int streamType = 3; int sampleRateInHz = 44100; // public static final int CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT); int channelConfig = (0x4 | 0x8); // public static final int ENCODING_PCM_16BIT = 2; int audioFormat = 2; // getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) jmethodID jGetMinBufferSizeMid = env->GetStaticMethodID(jAudioTrackClass, "getMinBufferSize", "(III)I"); int bufferSizeInBytes = env->CallStaticIntMethod(jAudioTrackClass, jGetMinBufferSizeMid, sampleRateInHz, channelConfig, audioFormat); // public static final int MODE_STREAM = 1; int mode = 1; //创建了AudioTrack jobject jAudioTrack = env->NewObject(jAudioTrackClass,jAudioTrackCMid, streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode); //play方法 jmethodID jPlayMid = env->GetMethodID(jAudioTrackClass,"play","()V"); env->CallVoidMethod(jAudioTrack,jPlayMid); // write method jAudioTrackWriteMid = env->GetMethodID(jAudioTrackClass, "write", "([BII)I"); return jAudioTrack; } extern "C" JNIEXPORT void JNICALL Java_dc_test_ffmpeg_Test04PlayAudioActivity_printAudioInfo2(JNIEnv *env, jobject instance,jstring url_) { const char *url = env->GetStringUTFChars(url_, 0); LOGE("待播放地址%s",url); } extern "C" JNIEXPORT void JNICALL Java_dc_test_ffmpeg_Test04PlayAudioActivity_nativePlay(JNIEnv *env, jobject instance,jstring url_) { const char *url = env->GetStringUTFChars(url_, 0); LOGE("待播放地址%s",url_); AVFormatContext *pFormatContext = NULL; AVCodecParameters *pCodecParameters = NULL; AVCodec *pCodec = NULL; int formatFindStreamInfoRes = 0; int audioStramIndex = 0; AVCodecContext *pCodecContext = NULL; int codecParametersToContextRes = -1; int codecOpenRes = -1; AVPacket *pPacket = NULL; AVFrame *pFrame = NULL; int index = 0; int outChannels; int dataSize; uint8_t *resampleOutBuffer; jbyte *jPcmData; SwrContext *swrContext = NULL; int64_t out_ch_layout; int out_sample_rate; enum AVSampleFormat out_sample_fmt; int64_t in_ch_layout; enum AVSampleFormat in_sample_fmt; int in_sample_rate; int swrInitRes; ///1、初始化所有组件,只有调用了该函数,才能使用复用器和编解码器(源码) av_register_all(); ///2、打开文件 int open_input_result = avformat_open_input(&pFormatContext,url,NULL,NULL); if (open_input_result != 0){ LOGE("format open input error: %s", av_err2str(open_input_result)); goto _av_resource_destroy; } ///3.填充流信息到 pFormatContext formatFindStreamInfoRes = avformat_find_stream_info(pFormatContext, NULL); if (formatFindStreamInfoRes < 0) { LOGE("format find stream info error: %s", av_err2str(formatFindStreamInfoRes)); goto _av_resource_destroy; } ///4.、查找音频流的 index,后面根据这个index处理音频 audioStramIndex = av_find_best_stream(pFormatContext, AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1,NULL, 0); if (audioStramIndex < 0) { LOGE("format audio stream error:"); goto _av_resource_destroy; } ///4、查找解码器 //audioStramIndex 上一步已经获取了,通过音频流的index,可以从pFormatContext中拿到音频解码器的一些参数 pCodecParameters = pFormatContext->streams[audioStramIndex]->codecpar; pCodec = avcodec_find_decoder(pCodecParameters->codec_id); LOGE("采样率:%d", pCodecParameters->sample_rate); LOGE("通道数: %d", pCodecParameters->channels); LOGE("format: %d", pCodecParameters->format); if (pCodec == NULL) { LOGE("codec find audio decoder error"); goto _av_resource_destroy; } LOGE("之前的跑完了,下面开始解码"); ///5、打开解码器 //分配AVCodecContext,默认值 pCodecContext = avcodec_alloc_context3(pCodec); if (pCodecContext == NULL){ LOGE("avcodec_alloc_context3 error"); goto _av_resource_destroy; } //pCodecParameters 转 context codecParametersToContextRes = avcodec_parameters_to_context(pCodecContext,pCodecParameters); if(codecParametersToContextRes <0){ LOGE("avcodec_parameters_to_context error"); goto _av_resource_destroy; } // codecOpenRes = avcodec_open2(pCodecContext,pCodec,NULL); if (codecOpenRes != 0) { LOGE("codec audio open error: %s", av_err2str(codecOpenRes)); goto _av_resource_destroy; } LOGE("解码器初始化完成"); //到此,pCodecContext 已经初始化完毕,下面可以用来获取每一帧数据 pPacket = av_packet_alloc(); pFrame = av_frame_alloc(); ///创建java 的 AudioTrack audioTrack = initAudioTrack(env); LOGE("初始化 AudioTrack完成"); // ---------- 重采样 构造 swrContext 参数 start---------- out_ch_layout = AV_CH_LAYOUT_STEREO; out_sample_fmt = AVSampleFormat::AV_SAMPLE_FMT_S16; out_sample_rate = AUDIO_SAMPLE_RATE; in_ch_layout = pCodecContext->channel_layout; in_sample_fmt = pCodecContext->sample_fmt; in_sample_rate = pCodecContext->sample_rate; swrContext = swr_alloc_set_opts(NULL, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL); if (swrContext == NULL) { // 提示错误 LOGE("swr_alloc_set_opts error"); goto _av_resource_destroy; } swrInitRes = swr_init(swrContext); if (swrInitRes < 0) { LOGE("swr_init error"); goto _av_resource_destroy; } LOGE("重采样 完成"); // ---------- 重采样 构造 swrContext 参数 end---------- // size 是播放指定的大小,是最终输出的大小 outChannels = av_get_channel_layout_nb_channels(out_ch_layout); //通道数 dataSize = av_samples_get_buffer_size(NULL, outChannels, pCodecParameters->frame_size,out_sample_fmt, 0); resampleOutBuffer = (uint8_t *) malloc(dataSize); //一帧一帧播放,while循环 while (av_read_frame(pFormatContext,pPacket) >=0){ LOGE("单帧 01"); // Packet 包,压缩的数据,解码成 pcm 数据 //判断是音频帧 if (pPacket->stream_index != audioStramIndex) { continue; } LOGE("单帧 02"); //输入原数据到解码器 int codecSendPacketRes = avcodec_send_packet(pCodecContext,pPacket); if (codecSendPacketRes == 0){ LOGE("单帧 05"); //解码器输出解码后的数据 pFrame AVCodecContext *avctx, AVFrame *frame int codecReceiveFrameRes = avcodec_receive_frame(pCodecContext,pFrame); if(codecReceiveFrameRes == 0){ index++; LOGE("单帧 06"); //数据转换成Buffer,需要导入 libswresample/swresample.h swr_convert(swrContext, &resampleOutBuffer, pFrame->nb_samples, (const uint8_t **) pFrame->data, pFrame->nb_samples); LOGE("单帧 06.1"); jbyteArray jPcmDataArray = env->NewByteArray(dataSize); // native 创建 c 数组 jPcmData = env->GetByteArrayElements(jPcmDataArray, NULL); //内存拷贝 memcpy(jPcmData, resampleOutBuffer, dataSize); // 同步刷新到 jbyteArray ,并释放 C/C++ 数组 env->ReleaseByteArrayElements(jPcmDataArray, jPcmData, 0); ///public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {} env->CallIntMethod(audioTrack, jAudioTrackWriteMid, jPcmDataArray, 0, dataSize); LOGE("解码第 %d 帧dataSize =%d ", index , dataSize); // 解除 jPcmDataArray 的持有,让 javaGC 回收 env->DeleteLocalRef(jPcmDataArray); } LOGE("单帧 07"); } LOGE("单帧 03"); //解引用 av_packet_unref(pPacket); av_frame_unref(pFrame); LOGE("单帧 04"); } LOGE("逐帧播放 完成"); /// 解引用数据 data , 2. 销毁 pPacket 结构体内存 3. pPacket = NULL av_frame_free(&pFrame); av_packet_free(&pPacket); _av_resource_destroy: if (pFormatContext != NULL){ avformat_close_input(&pFormatContext); avformat_free_context(pFormatContext); pFormatContext = NULL; } env->ReleaseStringUTFChars(url_, url); }
然后在Activity调用
package dc.test.ffmpeg import android.Manifest import android.content.Context import android.content.Intent import dc.android.base.activity.BridgeActivity import dc.android.libs.PermissionUtils import dc.android.libs.permission.AbsPermissionCallback import dc.common.Logger /** * * * @ClassName: Test04PlayAudioActivity * @author senrsl * * @Package: dc.test.ffmpeg * @CreateTime: 2020/12/14 5:48 下午 */ class Test04PlayAudioActivity : BridgeActivity() { init { System.loadLibrary("ffutils") } external fun printAudioInfo2(path: String) external fun nativePlay(path: String) companion object { @JvmStatic fun start(context: Context) { val starter = Intent(context, Test04PlayAudioActivity::class.java) context.startActivity(starter) } } override fun initData() { super.initData() PermissionUtils.with(this) .permisson(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE) .callback(object : AbsPermissionCallback() { override fun onResult(isAllGrant: Boolean, hasDenied: Boolean, hasRationale: Boolean) { Logger.w(this@Test04PlayAudioActivity, "授权$isAllGrant") //android 11 /mnt/sdcard/ 这个路径不好使了,不断做死的google。。。。 //val path = "/mnt/sdcard/SENRSL/audio03.mp3" val path = "/sdcard/SENRSL/audio03.mp3" //val file = File(path) printAudioInfo2(path) nativePlay(path) } }).request() } }
3,不断作死的google
想看看内存CPU占用,结果发现 这个版本的android studio profiler工具又不工作了。。。。
不错,这很google。。。。
折腾了半天,依然不显示曲线。。。。。。
更新了 idea 2020.2.4之后,又出现了重启idea后,code style会恢复成 project.....
每次都得重新设置code style为 default。。。。。
bug修好了没几天,又出现了。。。。
设备频繁连不上
SENRSL:Downloads senrsl$ adb connect 192.168.7.210
adb server version (40) doesn't match this client (41); killing...
* daemon started successfully
error: protocol fault (couldn't read status length): Undefined error: 0
看起来是本机两套adb环境,一套给eclipse,一套给 idea,
最近升级了idea,但没升级 eclipse。。。。
然后 指向android sdk home 为 idea 的 sdkas就好了。
或者升级eclipse的sdk 也可以。。。。
然后,启动了idea后又出现了,但是变成了几率性的。。。。
java.lang.IllegalArgumentException: Zero length string passed to TextLayout constructor.
at java.desktop/java.awt.font.TextLayout.<init>(TextLayout.java:382)
at java.desktop/java.awt.Font.getStringBounds(Font.java:2617)
at java.desktop/java.awt.Font.getStringBounds(Font.java:2522)
at com.android.tools.adtui.instructions.TextInstruction.<init>(TextInstruction.java:38)
at com.android.tools.adtui.LegendComponent.modelChanged(LegendComponent.java:232)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at com.android.tools.adtui.model.AspectModel$Dependency.changed(AspectModel.java:82)
at com.android.tools.adtui.model.AspectModel$Dependency.access$100(AspectModel.java:67)
at com.android.tools.adtui.model.AspectModel.lambda$changed$0(AspectModel.java:32)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at com.android.tools.adtui.model.AspectModel.changed(AspectModel.java:32)
at com.android.tools.adtui.model.legend.LegendComponentModel.lambda$new$0(LegendComponentModel.java:40)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at com.android.tools.adtui.model.AspectModel$Dependency.changed(AspectModel.java:82)
at com.android.tools.adtui.model.AspectModel$Dependency.access$100(AspectModel.java:67)
at com.android.tools.adtui.model.AspectModel.lambda$changed$0(AspectModel.java:32)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at com.android.tools.adtui.model.AspectModel.changed(AspectModel.java:32)
at com.android.tools.adtui.model.Range.set(Range.java:60)
at com.android.tools.adtui.model.StreamingTimeline.reset(StreamingTimeline.java:462)
at com.android.tools.profilers.StudioProfilers.lambda$new$1(StudioProfilers.java:247)
at com.android.tools.profilers.StudioProfilers.selectedSessionChanged(StudioProfilers.java:648)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at com.android.tools.adtui.model.AspectModel$Dependency.changed(AspectModel.java:82)
at com.android.tools.adtui.model.AspectModel$Dependency.access$100(AspectModel.java:67)
at com.android.tools.adtui.model.AspectModel.lambda$changed$0(AspectModel.java:32)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at com.android.tools.adtui.model.AspectModel.changed(AspectModel.java:32)
at com.android.tools.profilers.sessions.SessionsManager.setSessionInternal(SessionsManager.java:364)
at com.android.tools.profilers.sessions.SessionsManager.lambda$updateSessionItemsByGroup$2(SessionsManager.java:271)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at com.android.tools.profilers.sessions.SessionsManager.updateSessionItemsByGroup(SessionsManager.java:247)
at com.android.tools.profilers.sessions.SessionsManager.updateSessions(SessionsManager.java:229)
at com.android.tools.profilers.sessions.SessionsManager.update(SessionsManager.java:216)
at com.android.tools.profilers.StudioProfilers.update(StudioProfilers.java:488)
at com.android.tools.adtui.model.updater.Updater.lambda$onTick$0(Updater.java:103)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at com.android.tools.adtui.model.updater.Updater.onTick(Updater.java:103)
at com.android.tools.adtui.model.StopwatchTimer.tick(StopwatchTimer.java:55)
at com.android.tools.adtui.model.FpsTimer.actionPerformed(FpsTimer.java:73)
at java.desktop/javax.swing.Timer.fireActionPerformed(Timer.java:317)
at java.desktop/javax.swing.Timer$DoPostEvent.run(Timer.java:249)
at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:776)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:746)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:971)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:841)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:452)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:744)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$9(IdeEventQueue.java:451)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:802)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:505)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
好吧,不折腾了,还是 接个leakcanary。。。。。
4,ffmpeg推流警告
[flv @ 0x7fe4ed01fe00] Failed to update header with correct
duration.1770.2kbits/s speed= 1x
[flv @ 0x7fe4ed01fe00] Failed to update header with correct
filesize.
增加 -flvflags no_duration_filesize
5,IIS配置
IIS Server 需要配置MIME,不然会404
- . -> application/octet-stream 用于无扩展名文件
- .ico -> image/x-icon
- .properties -> text/plain
- .apk -> application/vnd.android.package-archive
茅台抢不到,只好买了两瓶五粮液,1100一瓶,真他娘的贵啊。。。。
八代普五为啥 NFC没反应,只震动不显示结果。。。。
算了,重开一贴再水。。。。
2020年12月22日10:56:23
senRsl
2020年12月14日10:55:49
没有评论 :
发表评论