Android FFmpeg 解码 OpenSL ES 播放音频

这篇具有很好参考价值的文章主要介绍了Android FFmpeg 解码 OpenSL ES 播放音频。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 在Android开发中,OpenSLES(Open Sound Library for Embedded Systems)是一个 C/C++ 音频库,提供了底层的音频功能和处理接口。它是 Android 平台上用于实现低延迟和高性能音频功能的一种选择。

本文的主线任务是描述 一个媒体文件通过 FFmpeg 解码后用 OpenSL ES 播放音频的过程

因为代码量很多,所以我直接从 Native 层开始了,看不懂的可以下载源代码配合着看(末尾)


extern "C"
JNIEXPORT void JNICALL
Java_cn_wk_opensl_1demo_MainActivity_audioPlayer(JNIEnv *env, jobject thiz, jstring dataStr) {
    const char *dataSource = env->GetStringUTFChars(dataStr, nullptr);

    pthread_create(&pid_prepare, nullptr, task_prepare, (void *) dataSource);
}

这是 JNI 函数,上层传递媒体文件全路径到 Native 层(因为 FFmpeg 读取文件需要),之后开启准备线程(就是要开始进行异步对文件做处理了)

/**
 * FFmpeg 对媒体文件 做处理
 */
void *task_prepare(void *args) {
    const char *data_source = (const char *) args;
    LOGI("data_source: %s", data_source)

    formatContext = avformat_alloc_context(); // 给 媒体上下文 开辟内存
    av_dict_set(&dictionary, "timeout", "5000000", 0); // 设置字典参数

    // TODO 打开媒体地址(如:文件路径,直播地址rtmp等)
    avformat_open_input(&formatContext, data_source, nullptr, &dictionary);
    // 释放字典(用完就释放)
    av_dict_free(&dictionary);

    // TODO 查找媒体中的音视频流的信息
    avformat_find_stream_info(formatContext, nullptr);

    // TODO 根据流信息,把 音频流、视频流 分开处理
    for (int stream_index = 0; stream_index < formatContext->nb_streams; ++stream_index) {
        AVStream *stream = formatContext->streams[stream_index];      // 获取媒体流(视频,音频)
        AVCodecParameters *parameters = stream->codecpar;             // 从流中获取 编解码 参数
        AVCodec *codec = avcodec_find_decoder(parameters->codec_id);  // 获取编解码器
        AVCodecContext *codecContext = avcodec_alloc_context3(codec); // 给 codecContext 开辟内存
        avcodec_parameters_to_context(codecContext, parameters);      // codecContext 初始化
        avcodec_open2(codecContext, codec, nullptr);          // 打开 编解码器

        // 从编解码参数中,区分流的类型,分别处理 (codec_type == 音频流/视频流/字幕流)
        if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
            LOGI("音频流")
            audio_channel = new AudioChannel(codecContext); // codecContext 才是真正干活的
            audio_channel->start(); // 开启 解码线程 和 播放线程

            pthread_create(&pid_start, nullptr, task_start, nullptr); // 数据传输线程
        } else if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
            LOGI("视频流")
        } else if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_SUBTITLE) {
            LOGI("字幕流")
        }
    }
    return nullptr; // 函数的返回值是 void* 必须返回 nullptr
}

总结:把文件路径给到 FFmpeg读取媒体文件,读取后媒体文件的流分开操作(视频搞视频的,音频搞音频的),这里音频处理封装了 AudioChannel 这个类。

需要注意的是,我为了尽可能减少代码省略了很多 FFmpeg 函数的返回值,比如 avcodec_parameters_to_context() 和 avcodec_open2() 都是有返回值的,非0为失败,可以自行对错误做处理


可以看到又开启了一个线程:task_start

/**
 * 将 AVPacket 传给 AudioChannel
 */
void *task_start(void *args) {
    while (1) {
        if (audio_channel && audio_channel->packets.size() > 100) {
            av_usleep(10 * 1000); // FFmpeg 的时间是微秒,所以这个是10毫秒
            continue;
        }
        AVPacket *packet = av_packet_alloc();             // 给 AVPacket 开辟内存
        int ret = av_read_frame(formatContext, packet);   // 从 formatContext 读帧赋值到 AVPacket
        if (!ret) {
            audio_channel->packets.insertToQueue(packet);
        } else {
            break;
        }
    }
    return nullptr;
}

工具线程:将 FFmpeg 读取到的 AVPacket 传给 AudioChannel 而已


重头戏:AudioChannel

void AudioChannel::start() {
    isPlaying = 1;

    // 队列开始工作
    packets.setWork(1);
    frames.setWork(1);

    // 音频解码线程
    pthread_create(&pid_audio_decode, nullptr, task_audio_decode, this);
    // 音频播放线程
    pthread_create(&pid_audio_play, nullptr, task_audio_play, this);
}

开启两个线程:一个解码线程,一个播放线程(FFmpeg 解码,OpenSL ES 播放)

void *task_audio_decode(void *args) {  // 很头痛,C的子线程函数必须是这个格式,所以要包装一层....
    auto *audio_channel = static_cast<AudioChannel *>(args);
    audio_channel->audio_decode();
    return nullptr;
}

/**
 * 音频解码:codecContext 把 AVPacket 解码为 AVFrame
 */
void AudioChannel::audio_decode() {
    AVPacket *pkt = nullptr;
    while (isPlaying) {
        if (isPlaying && frames.size() > 100) {
            av_usleep(10 * 1000);
            continue;
        }
        int ret = packets.getQueueAndDel(pkt);
        if (!ret) {
            continue; // 生产-消费模型,所以可能会失败,重来就行
        }
        // TODO 把 AVPacket 给 codecContext 解码
        ret = avcodec_send_packet(codecContext, pkt);

        AVFrame *frame = av_frame_alloc(); // 给 AVFrame 开辟内存

        // TODO 从 codecContext 中拿解码后的产物 AVFrame
        ret = avcodec_receive_frame(codecContext, frame);

        if (ret == AVERROR(EAGAIN))
            continue; // 音频帧可能获取失败,重新拿一次

        // 原始包 AVFrame 加入播放队列
        frames.insertToQueue(frame);
    }
}

总结:FFmpeg 将 AVPacket 解码为 AVFrame 并塞进播放队列

接下来是播放线程:

void *task_audio_play(void *args) {  // 头痛头痛
    auto *audio_channel = static_cast<AudioChannel *>(args);
    audio_channel->audio_play();
    return nullptr;
}

/**
 * 音频播放
 */
void AudioChannel::audio_play() {
    SLresult result; // 用于接收 执行成功或者失败的返回值

    // TODO 创建引擎对象并获取【引擎接口】
    slCreateEngine(&engineObject, 0, 0,
                   0, 0, 0);
    (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);

    // TODO 创建、初始化混音器
    (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
    (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);

    // TODO 创建并初始化播放器
    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 10};
    SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, // PCM数据格式
                                   2, // 声道数
                                   SL_SAMPLINGRATE_44_1, // 采样率(每秒44100个点)
                                   SL_PCMSAMPLEFORMAT_FIXED_16, // 每秒采样样本 存放大小 16bit
                                   SL_PCMSAMPLEFORMAT_FIXED_16, // 每个样本位数 16bit
                                   SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, // 前左声道  前右声道
                                   SL_BYTEORDER_LITTLEENDIAN};
    SLDataSource audioSrc = {&loc_bufq, &format_pcm};
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&loc_outmix, NULL};
    const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};
    (*engineInterface)->CreateAudioPlayer(engineInterface, &bqPlayerObject, &audioSrc, &audioSnk, 1, ids, req);
    (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);

    // TODO 设置回调函数
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
    (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);

    // TODO 设置播放状态
    (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);

    // 6.手动激活回调函数
    bqPlayerCallback(bqPlayerBufferQueue, this);
}

怎么样?头痛吗,我也很头痛,代码量真的挺多的(我还把返回值去掉了的),这个 OpenSL ES 的使用真的没十年脑血栓设计不出来,其实基本上都是样板代码,大概知道什么意思就行了,关键是回调函数:

/**
 * 真正播放的函数,这个函数会一直调用
 * 关键是 SLAndroidSimpleBufferQueueItf 这个结构体,往这个结构体的队列加 PCM 数据就能播放了
 */
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *args) {
    auto *audio_channel = static_cast<AudioChannel *>(args);
    int pcm_size = audio_channel->getPCM();

    (*bq)->Enqueue(bq, audio_channel->out_buffers, pcm_size);
}

 播放音频的关键就是这个,往 Enqueue 上加 PCM 数据就能播放了


本篇文章仅实现了 FFmpeg 和 OpenGL ES 配和播放媒体文件音频的功能,其中有非常多的细节没有去完善(比如函数错误返回值的处理、内存泄漏等等),因为我为了更好的阅读和理解 FFmpeg 和 OpenSL ES,对非主线代码做了删减,所以读者可以自行添加

源代码链接:https://github.com/yinwokang/Android-OpenSLES/文章来源地址https://www.toymoban.com/news/detail-481991.html

到了这里,关于Android FFmpeg 解码 OpenSL ES 播放音频的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ffmpeg学习之音频解码数据

    音频数据经过解码后会被保存为,pcm数据格式。而对应的处理流程如下所示。 avcodec_find_encoder()  avcodec_find_encoder_by_name()  avcodec_alloc_context3()   设置对应音频编码的数据类型 设置编码的frame的相关参数 整个代码:

    2024年02月16日
    浏览(40)
  • FFmpeg 解码 AAC 格式的音频

    FFmpeg 默认是可以解码 AAC 格式的音频,但是如果需要获取 PCM16 此类数据则需要经过音频转码。首先要打开解码器,然后向解码器发送 AAC 音频帧(不带 ADTS),然后从解码器获取解码后的音频帧,数据是 float 类型的,如果需要则进行转码流程将 float 转成整型。 一、AAC 音频

    2024年02月11日
    浏览(54)
  • FFmpeg音频解码流程详解及简单demo参考

            本文主要讲解FFmpeg的音频解码具体流程,API使用。最后再以一个非常简单的demo演示将一个mp3格式的音频文件解码为原始数据pcm文件。 本文主要基于FFmpeg音频解码新接口。    API接口简单大体讲解如下:         这一步是ffmpeg的任何程序的第一步都是需要先注

    2023年04月08日
    浏览(50)
  • Android 音视频开发—MediaPlayer音频与视频的播放介绍

    Android多媒体中的——MediaPlayer,我们可以通过这个API来播放音频和视频该类是Androd多媒体框架中的一个重要组件,通过该类,我们可以以最小的步骤来获取,解码和播放音视频。 它支持三种不同的媒体来源: 本地资源 内部的URI,比如你可以通过ContentResolver来获取 外部URL(流

    2024年02月10日
    浏览(60)
  • 利用ffmpeg和opencv进行视频的解码播放

    引子 OpenCV中有自己的用于处理图片和视频的类VideoCapture,可以很方便的读入文件和显示。 现在视频数据流是ffmpeg解码h264文件得到的,由于要依赖该数据源进行相应的后续处理,所以需要将ffmpeg中得到的数据缓存转换成可以被OpenCV处理的Mat类对象。 ffmpeg介绍 FFmpeg是一个开源

    2024年02月13日
    浏览(45)
  • 深入浅出:FFmpeg 音频解码与处理AVFrame全解析

    FFmpeg 是一个开源的音视频处理软件,它包含了一系列的库和程序,用于处理音频、视频和其他多媒体数据。FFmpeg 的名字来源于 “Fast Forward MPEG”,其中 MPEG 是一种常见的音视频编码标准。 FFmpeg 项目于 2000 年由 Fabrice Bellard 启动,他是 QEMU(一种开源的计算机模拟器和虚拟机

    2024年02月04日
    浏览(112)
  • 基于FFMpeg实现音频mp3/aac/wav解码

    编译环境:Ubuntu16.04 64位 交叉编译工具:arm-himix200-linux-gcc 我这里使用的是ffmpeg-5.1.2.tar.gz,下载地址点击下载地址。 这样,/root/ffmpeg-5.1.2/output下面就是咱们要的程序,bin目录下ffmpeg可以在开发板上运行,include下是需要的头文件,lib下是需要的静态库,share/ffmpeg/examples是一些

    2024年02月11日
    浏览(59)
  • ExoPlayer(AndroidX Media3) 扩展ffmpeg实现音频软解码

    1.Ubuntu 20.04.4 LTS 2.AndroidNDK版本r26C 3.AndroidStudio 2023.1.1(配置好SDK和JDK 17.0.10) 4.ffmpeg6.0源码 5.ExoPlayer源码,AndroidX Media release分支版本 目前官方已废弃Exopler2,代码已经迁移到AndroidX Media,下载完成设置FFMPEG_MODULE_PATH变量 1. git clone https://github.com/androidx/media 2. cd media FFMPEG_MODULE_PATH

    2024年04月12日
    浏览(123)
  • 【Qt+FFmpeg】鼠标滚轮放大、缩小、移动——解码播放本地视频(三)

     上一期我们实现了播放、暂停、重播、倍速功能,这期来谈谈如何实现鼠标滚轮放大缩小和移动;如果还没看过上期,请移步 【Qt+FFmpeg】解码播放本地视频(一)_logani的博客-CSDN博客【Qt+FFmpeg】解码播放本地视频(二)——实现播放、暂停、重播、倍速功能_logani的博客-C

    2024年02月10日
    浏览(44)
  • ES8311 - 音频编解码芯片调试

    目录 前言 ES8311 codec芯片介绍 调通的配置 软件配置 ID 验证 回环测试 注意事项: 最近因任务需求,需要将一款codec 芯片配合TTS调通,做某款云喇叭播放设备。 这款codec的驱动已经适配过多次,但在此系统上却是第一次调试,谨做记录。 System High performance and low power multi-bi

    2024年02月02日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包