Android 万能音频播放器 一 C++多线程解码音频数据

编程入门 行业动态 更新时间:2024-10-22 13:51:42

Android 万能音频播放器一 C++多线程解码音频数据

目录

  1. Android 万能音频播放器 一 C++多线程解码音频数据
  2. Android 万能音频播放器 二 C++队列存放AvPacket
  3. OpenSL介绍 并实现播放PCM功能

前言

如果对Android NDK 下的C++ 多线程不了解的话可以看这篇文章Android NDK 多线程 本系列将用到Android NDK,C++,FFmpeg等相关知识,努力做好SDK的封装,方便以后在项目中可以复用。

本章实现

FFmpeg在C++子线程中解码音频数据,得到数据包AVPacket
AVPacket:存放原始音频/视频的压缩包

实现步骤
  1. 注册解码器并初始化网络
  2. 打开文件或网络流
  3. 获取流信息
  4. 获取音频流
  5. 获取解码器
  6. 打开解码器
  7. 读取音频帧
FFmpeg解码流程(代码)

运行截图

代碼結構

目录结构

音频解码信息封装


//音频解码所需信息封装
class WIAudio {
public:
    //音頻流索引浩
    int audio_stream_index;
    //音頻流信息
    AVCodecParameters  *av_codec_par = NULL;
    //音频解码器上下文
    AVCodecContext * av_codec_ctx = NULL;
    WIAudio();
    ~WIAudio();
};

回调java接口封装

class WIPreparedListener {

private:
    //java虚拟机类 用来获取JNIEnv对象 因为每个线程都有自己的env
    JavaVM *javaVM = NULL;
    //JNI环境对象
    JNIEnv * env = NULL;
    //回调对象 一般方法用jobject 静态方法用jclass
    jobject  listener_obj;
    //回调方法的id
    jmethodID  prepare_mid;
public:
    WIPreparedListener(JavaVM *javaVM,JNIEnv * env,jobject  listener_obj);
    ~WIPreparedListener();
    void onPrepared(int type);
};

音频解码封装

class WIPlayer {
public:
    //音频解码所需属性封装
    WIAudio *audio = NULL;
    //初始化回调java方法封装
    WIPreparedListener * listener;
    //播放URL
    const char * url = NULL;
    //封装上下文
    AVFormatContext * av_format_ctx = NULL;
    //解码线程标识
    pthread_t decodeThread;
public:
    WIPlayer(const char * url,WIPreparedListener * listener);
    ~WIPlayer();
    void prepare();
    void audioDecodeThread();
    void start();
};

解码具体实现

#include "WIPlayer.h"

WIPlayer::WIPlayer(const char *url, WIPreparedListener *listener) {
    this->url = url;
    this->listener = listener;
}

WIPlayer::~WIPlayer() {

}

void *decodeFFmpeg(void *data) {
    //强制类型转换
    WIPlayer *player = (WIPlayer *) data;
    player->audioDecodeThread();
    pthread_exit((void *) player->decodeThread);
}

void WIPlayer::prepare() {
    //创建子线程 并将本身传入
    pthread_create(&decodeThread, NULL, decodeFFmpeg, this);
}

void WIPlayer::audioDecodeThread() {
    //注册所有组件并初始化网络
    av_register_all();
    avformat_network_init();

    av_format_ctx = avformat_alloc_context();

    //打开文件或网络流
    if (avformat_open_input(&av_format_ctx, url, NULL, NULL) != 0) {
        LOGE("can not open url :%s", url);
        return;
    }

    //获取流信息
    if (avformat_find_stream_info(av_format_ctx, NULL) < 0) {
        LOGE("can not find streams from %s", url);
        return;
    }

    //根据codecpar->codec_type获取音频流索引和音频流信息 此版本不能用streams[i]->codec来直接获取解码上下文
    for (int i = 0; i < av_format_ctx->nb_streams; ++i) {
        if (av_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            if (audio == NULL) {
                audio = new WIAudio();
                audio->audio_stream_index = i;
                audio->av_codec_par = av_format_ctx->streams[i]->codecpar;
            }
        }
    }

    //获取解码器
    AVCodec *decoder = avcodec_find_decoder(audio->av_codec_par->codec_id);

    if (decoder == NULL) {
        LOGE("can not find decoder from %s", url);
        return;
    }

    //申请解码上下文
    audio->av_codec_ctx = avcodec_alloc_context3(decoder);

    if (!audio->av_codec_ctx) {
        LOGE("can not alloc accodecxtx");
        return;
    }

    //将音频流信息填充到解码上下文
    if (avcodec_parameters_to_context(audio->av_codec_ctx, audio->av_codec_par) < 0) {
        LOGE("can not parameters to context");
        return;
    }

    //打开解码器
    if (avcodec_open2(audio->av_codec_ctx, decoder, 0) != 0) {
        LOGE("can not open decoder from %s", url);
        return;
    }

    if (listener != NULL) {
        listener->onPrepared(CHILD_THREAD);
    }
}

void WIPlayer::start() {
    if (audio == NULL) {
        LOGE("audio is empty");
        return;
    }

    int count = 0;
    //开始解码
    while (1) {
        //新api提供了avpacket的申请方式 。此语句不能再while外面 因为申请的已经释放,必须重新申请
        AVPacket *avPacket = av_packet_alloc();
        if (av_read_frame(av_format_ctx, avPacket) == 0) {
            if (avPacket->stream_index == audio->audio_stream_index) {
                count++;
                LOGE("解码第%d帧", count);
                av_packet_free(&avPacket);
                av_free(avPacket);
            } else {
                //释放avpacket
                av_packet_free(&avPacket);
                av_free(avPacket);
            }
        } else {
            LOGE("解码结束共%d帧", count);
            //释放avpacket
            av_packet_free(&avPacket);
            av_free(avPacket);
            break;
        }
    }
}

源码地址

更多推荐

Android 万能音频播放器 一 C++多线程解码音频数据

本文发布于:2023-06-13 22:40:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1412568.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:多线程   音频   音频播放器   数据   Android

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!