東川印記

一本東川,笑看爭龍斗虎;寰茫兦者,度橫佰昧人生。

Android rtmp直播03 FFmpeg Android so的简单用法

2020年12月22日星期二



项目结构改了亿点点

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

  1. . -> application/octet-stream 用于无扩展名文件
  2. .ico -> image/x-icon
  3. .properties  -> text/plain
  4. .apk  -> application/vnd.android.package-archive

茅台抢不到,只好买了两瓶五粮液,1100一瓶,真他娘的贵啊。。。。



八代普五为啥 NFC没反应,只震动不显示结果。。。。

算了,重开一贴再水。。。。

2020年12月22日10:56:23


--
senRsl
2020年12月14日10:55:49

没有评论 :

发表评论