基于FFmpeg和OpenSL ES的Android音频播放器实现

Android音乐播放器-使用FFmpeg及OpenSLES 在之前的文章《解码音频代码》中,已经实现了使用FFmpeg解码音频为PCM。这次我们利用FFmpeg以及OpenSLES来实现一个简单的音乐播放器。 阅读详情

前言

在以前的博文中,我们通过FFmpeg解码,并基于OpenGL ES完成了视频的渲染。本文我们将基于OpenSL ES完成native音频的注入播放。
OpenSL ES也是The Khronos Group Inc组织制定的一个音频规范,网上资料很多,在Android SDK代码里还有例子:
native-audio,里面详细的实现了OpenSL ES的不同功能,本文不再阐述原理了,基本上是按照固定流程调用。
当我们基于FFmpeg解码时,最合适的方法是采用buffer管理的方式,将音频PCM数据连续注入解码内存,再由芯片完成PCM的播放。有关采样率,声道存储位宽等音频相关知识,请自行学习。

实现需求

  1. 只演示音频解码和播放,不进行视频解码,也不做音视频同步;
  2. 基于FFmpeg来进行音频解封装和解码,基于OpenSL ES进行PCM注入播放;

实现架构

仍然类似于前面视频播放的播放流程设计,由于不涉及surface的管理,所以要简单一些,注意下面几点:
1. createEngine()和createBufferQueueAudioPlayer()函数放在打开媒体文件之后,因为要依赖媒体的音频属性(声道数,采样率等);
2. 播放器PCM数据的读取是通过回调的方式从媒体队列中读取并进行解码,而不是另建一个线程主动注入,这点与视频解码是不同的。
3. 因为音频的播放速度是由采样率决定的,所以音频的播放速率无需干预,不存在视频帧播放快慢的问题。
音频结构图

代码结构

代码结构3
audio-jni.cpp提供jni相关函数的实现,本地代码最终生成libaudio-jni.so库。
audio.cpp完成基于OpenSL ES的音频管理。
player.cpp里实现媒体文件的打开播放(基于FFmpeg)。

主要代码

下面是audio-jni.cpp的源码,这是本项目的核心实现。需要特别注意如下几点,否则音频无法播放成功:
1. createBufferQueueAudioPlayer()函数中,SLDataFormat_PCM format_pcm的第二个参数是声道数,第三个参数是采样率,FFmpeg和OpenSL ES差1000的倍乘,要注意的是参数channelMask,一定要和声道数对应,比如,如果是双声道,我们需要将两个声道通过或关系绑定(SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT),而不能简单的设置SL_SPEAKER_FRONT_CENTER,否则CreateAudioPlayer会失败,这是我遇到过的问题。
2. 播放器SetPlayState状态为SL_PLAYSTATE_PLAYING后,并不会主动调用回调函数来取数据,所以我们需要主动调用一次回调函数(通过fireOnPlayer函数)触发数据读取。
3. 回调函数读取数据并不会像SDL那样,告知底层需要读取的数据长度,所以我们可以做简单一点,仅读取一个包并解码回调即可,待数据消耗完毕,还会再次调用回调函数读PCM数据的,效率并不低。

#include <assert.h>
#include <jni.h>
#include <string.h>
#include "com_opensles_ffmpeg_MainActivity.h"
#include "player.h"

// for native audio
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

// engine interfaces
static SLObjectItf engineObject = NULL;
static SLEngineItf engineEngine;

// output mix interfaces
static SLObjectItf outputMixObject = NULL;
static SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;

// buffer queue player interfaces
static SLObjectItf bqPlayerObject = NULL;
static SLPlayItf bqPlayerPlay;
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
static SLEffectSendItf bqPlayerEffectSend;
static SLVolumeItf bqPlayerVolume;
static uint8_t decoded_audio_buf[AVCODEC_MAX_AUDIO_FRAME_SIZE];

// this callback handler is called every time a buffer finishes playing
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
    SLresult result;

    //LOGV2("bqPlayerCallback...");

    if (bq != bqPlayerBufferQueue) {
        LOGV2("bqPlayerCallback : not the same player object.");
        return;
    }

    int decoded_size = audio_decode_frame(decoded_audio_buf,
            sizeof(decoded_audio_buf));
    if (decoded_size > 0) {
        result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue,
                decoded_audio_buf, decoded_size);
        // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
        // which for this code example would indicate a programming error
        if (SL_RESULT_SUCCESS != result) {
            LOGV2("bqPlayerCallback : bqPlayerBufferQueue Enqueue failure.");
        }
    }
}

int createEngine() {

    SLresult result;

    // create engine
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("slCreateEngine failure.");
        return -1;
    }

    // realize the engine
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE );
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("engineObject Realize failure.");
        (*engineObject)->Destroy(engineObject);
        engineObject = NULL;
        engineEngine = NULL;
        return -1;
    }

    // get the engine interface, which is needed in order to create other objects
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE,
            &engineEngine);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("engineObject GetInterface failure.");
        (*engineObject)->Destroy(engineObject);
        engineObject = NULL;
        engineEngine = NULL;
        return -1;
    }

    // create output mix, with environmental reverb specified as a non-required interface
    const SLInterfaceID ids[1] = { SL_IID_ENVIRONMENTALREVERB };
    const SLboolean req[1] = { SL_BOOLEAN_FALSE };
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1,
            ids, req);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("engineObject CreateOutputMix failure.");
        (*engineObject)->Destroy(engineObject);
        engineObject = NULL;
        engineEngine = NULL;
        return -1;
    }

    // realize the output mix
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE );
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("outputMixObject Realize failure.");

        (*outputMixObject)->Destroy(outputMixObject);
        outputMixObject = NULL;
        outputMixEnvironmentalReverb = NULL;
        (*engineObject)->Destroy(engineObject);
        engineObject = NULL;
        engineEngine = NULL;
        return -1;
    }

    // get the environmental reverb interface
    // this could fail if the environmental reverb effect is not available,
    // either because the feature is not present, excessive CPU load, or
    // the required MODIFY_AUDIO_SETTINGS permission was not requested and granted
    result = (*outputMixObject)->GetInterface(outputMixObject,
            SL_IID_ENVIRONMENTALREVERB, &outputMixEnvironmentalReverb);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("outputMixObject Realize failure.");
        (*outputMixObject)->Destroy(outputMixObject);
        outputMixObject = NULL;
        outputMixEnvironmentalReverb = NULL;
        (*engineObject)->Destroy(engineObject);
        engineObject = NULL;
        engineEngine = NULL;
        return -1;
    }

    LOGV2("OpenSL ES createEngine success.");
    return 0;
}

int createBufferQueueAudioPlayer() {
    SLresult result;
    SLuint32 channelMask;

    // configure audio source
    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2 };

    if (global_context.acodec_ctx->channels == 2)
        channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
    else
        channelMask = SL_SPEAKER_FRONT_CENTER;

    SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM,
            global_context.acodec_ctx->channels,
            global_context.acodec_ctx->sample_rate * 1000,
            SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
            channelMask, SL_BYTEORDER_LITTLEENDIAN };

    SLDataSource audioSrc = { &loc_bufq, &format_pcm };

    // configure audio sink
    SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX,
            outputMixObject };
    SLDataSink audioSnk = { &loc_outmix, NULL };

    // create audio player
    const SLInterfaceID ids[3] = { SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND,
            SL_IID_VOLUME };
    const SLboolean req[3] =
            { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject,
            &audioSrc, &audioSnk, 3, ids, req);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("CreateAudioPlayer failure.");
        return -1;
    }

    // realize the player
    result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE );
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("bqPlayerObject Realize failure.");
        return -1;
    }

    // get the play interface
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY,
            &bqPlayerPlay);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("bqPlayerObject GetInterface failure.");
        return -1;
    }

    // get the buffer queue interface
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
            &bqPlayerBufferQueue);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("bqPlayerObject GetInterface failure.");
        return -1;
    }

    // register callback on the buffer queue
    result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue,
            bqPlayerCallback, NULL);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("bqPlayerObject RegisterCallback failure.");
        return -1;
    }

    // get the effect send interface
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND,
            &bqPlayerEffectSend);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("bqPlayerObject GetInterface SL_IID_EFFECTSEND failure.");
        return -1;
    }

    // get the volume interface
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME,
            &bqPlayerVolume);
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("bqPlayerObject GetInterface SL_IID_VOLUME failure.");
        return -1;
    }

    // set the player's state to playing
    result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING );
    if (SL_RESULT_SUCCESS != result) {
        LOGV2("bqPlayerObject SetPlayState SL_PLAYSTATE_PLAYING failure.");
        return -1;
    }

    LOGV2("OpenSL ES CreateAudioPlayer success.");

    return 0;
}

void fireOnPlayer() {
    bqPlayerCallback(bqPlayerBufferQueue, NULL);
}

/**
 * Destroys the given object instance.
 *
 * @param object object instance. [IN/OUT]
 */
static void DestroyObject(SLObjectItf& object) {
    if (0 != object)
        (*object)->Destroy(object);

    object = 0;
}

void destroyPlayerAndEngine() {
    // Destroy audio player object
    DestroyObject(bqPlayerObject);

    // Destroy output mix object
    DestroyObject(outputMixObject);

    // Destroy the engine instance
    DestroyObject(engineObject);
}

/*
 * Class:     com_opensles_ffmpeg_MainActivity
 * Method:    startAudioPlayer
 * Signature: ()I
 */JNIEXPORT jint JNICALL Java_com_opensles_ffmpeg_MainActivity_startAudioPlayer(
        JNIEnv *, jclass) {
    pthread_t thread;
    pthread_create(&thread, NULL, open_media, NULL);
    return 0;
}

/*
 * Class:     com_opensles_ffmpeg_MainActivity
 * Method:    destroyEngine
 * Signature: ()I
 */JNIEXPORT jint JNICALL Java_com_opensles_ffmpeg_MainActivity_destroyEngine(
        JNIEnv *, jclass) {
    destroyPlayerAndEngine();
    return 0;
}

/*
 * Class:     com_opensles_ffmpeg_MainActivity
 * Method:    stopAudioPlayer
 * Signature: ()I
 */JNIEXPORT jint JNICALL Java_com_opensles_ffmpeg_MainActivity_stopAudioPlayer(
        JNIEnv *, jclass) {
    (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED );
    global_context.pause = 1;
    global_context.quit = 1;
    usleep(50000);
    return 0;
}

下面是audio.cpp的源码,audio_decode_frame()函数通过FFmpeg的avcodec_decode_audio4()函数完成音频数据解码,init_filter_graph()函数,av_buffersrc_add_frame()函数,av_buffersink_get_frame()函数,完成解码后音频格式的统一转换,最后都变成AV_SAMPLE_FMT_S16的存储形式,当然采样率和声道数保持原始音频属性,这样做的好处是避免了不同音频格式解码出不同PCM存储类型。

#include "player.h"

#define DECODE_AUDIO_BUFFER_SIZE ((AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) )

static AVFilterContext *in_audio_filter;  // the first filter in the audio chain
static AVFilterContext *out_audio_filter;  // the last filter in the audio chain
static AVFilterGraph *agraph;              // audio filter graph
static struct AudioParams audio_filter_src;

static int init_filter_graph(AVFilterGraph **graph, AVFilterContext **src,
        AVFilterContext **sink) {
    AVFilterGraph *filter_graph;
    AVFilterContext *abuffer_ctx;
    AVFilter *abuffer;
    AVFilterContext *aformat_ctx;
    AVFilter *aformat;
    AVFilterContext *abuffersink_ctx;
    AVFilter *abuffersink;

    char options_str[1024];
    char ch_layout[64];

    int err;

    /* Create a new filtergraph, which will contain all the filters. */
    filter_graph = avfilter_graph_alloc();
    if (!filter_graph) {
        av_log(NULL, AV_LOG_ERROR, "Unable to create filter graph.\n");
        return AVERROR(ENOMEM);
    }

    /* Create the abuffer filter;
     * it will be used for feeding the data into the graph. */
    abuffer = avfilter_get_by_name("abuffer");
    if (!abuffer) {
        av_log(NULL, AV_LOG_ERROR, "Could not find the abuffer filter.\n");
        return AVERROR_FILTER_NOT_FOUND ;
    }

    abuffer_ctx = avfilter_graph_alloc_filter(filter_graph, abuffer, "src");
    if (!abuffer_ctx) {
        av_log(NULL, AV_LOG_ERROR,
                "Could not allocate the abuffer instance.\n");
        return AVERROR(ENOMEM);
    }

    /* Set the filter options through the AVOptions API. */
    av_get_channel_layout_string(ch_layout, sizeof(ch_layout), (int) 0,
            audio_filter_src.channel_layout);
    av_opt_set(abuffer_ctx, "channel_layout", ch_layout,
            AV_OPT_SEARCH_CHILDREN);
    av_opt_set(abuffer_ctx, "sample_fmt",
            av_get_sample_fmt_name(audio_filter_src.fmt),
            AV_OPT_SEARCH_CHILDREN);
    av_opt_set_q(abuffer_ctx, "time_base",
            (AVRational ) { 1, audio_filter_src.freq },
            AV_OPT_SEARCH_CHILDREN);
    av_opt_set_int(abuffer_ctx, "sample_rate", audio_filter_src.freq,
            AV_OPT_SEARCH_CHILDREN);

    /* Now initialize the filter; we pass NULL options, since we have already
     * set all the options above. */
    err = avfilter_init_str(abuffer_ctx, NULL);
    if (err < 0) {
        av_log(NULL, AV_LOG_ERROR,
                "Could not initialize the abuffer filter.\n");
        return err;
    }

    /* Create the aformat filter;
     * it ensures that the output is of the format we want. */
    aformat = avfilter_get_by_name("aformat");
    if (!aformat) {
        av_log(NULL, AV_LOG_ERROR, "Could not find the aformat filter.\n");
        return AVERROR_FILTER_NOT_FOUND ;
    }

    aformat_ctx = avfilter_graph_alloc_filter(filter_graph, aformat, "aformat");
    if (!aformat_ctx) {
        av_log(NULL, AV_LOG_ERROR,
                "Could not allocate the aformat instance.\n");
        return AVERROR(ENOMEM);
    }

    /* A third way of passing the options is in a string of the form
     * key1=value1:key2=value2.... */
    snprintf(options_str, sizeof(options_str),
            "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%x",
            av_get_sample_fmt_name(AV_SAMPLE_FMT_S16), audio_filter_src.freq,
            audio_filter_src.channel_layout);
    err = avfilter_init_str(aformat_ctx, options_str);
    if (err < 0) {
        av_log(NULL, AV_LOG_ERROR,
                "Could not initialize the aformat filter.\n");
        return err;
    }

    /* Finally create the abuffersink filter;
     * it will be used to get the filtered data out of the graph. */
    abuffersink = avfilter_get_by_name("abuffersink");
    if (!abuffersink) {
        av_log(NULL, AV_LOG_ERROR, "Could not find the abuffersink filter.\n");
        return AVERROR_FILTER_NOT_FOUND ;
    }

    abuffersink_ctx = avfilter_graph_alloc_filter(filter_graph, abuffersink,
            "sink");
    if (!abuffersink_ctx) {
        av_log(NULL, AV_LOG_ERROR,
                "Could not allocate the abuffersink instance.\n");
        return AVERROR(ENOMEM);
    }

    /* This filter takes no options. */
    err = avfilter_init_str(abuffersink_ctx, NULL);
    if (err < 0) {
        av_log(NULL, AV_LOG_ERROR,
                "Could not initialize the abuffersink instance.\n");
        return err;
    }

    /* Connect the filters;
     * in this simple case the filters just form a linear chain. */
    err = avfilter_link(abuffer_ctx, 0, aformat_ctx, 0);
    if (err >= 0) {
        err = avfilter_link(aformat_ctx, 0, abuffersink_ctx, 0);
    }

    if (err < 0) {
        av_log(NULL, AV_LOG_ERROR, "Error connecting filters\n");
        return err;
    }

    /* Configure the graph. */
    err = avfilter_graph_config(filter_graph, NULL);
    if (err < 0) {
        av_log(NULL, AV_LOG_ERROR, "Error configuring the filter graph\n");
        return err;
    }

    *graph = filter_graph;
    *src = abuffer_ctx;
    *sink = abuffersink_ctx;

    return 0;
}

static inline int64_t get_valid_channel_layout(int64_t channel_layout,
        int channels) {
    if (channel_layout
            && av_get_channel_layout_nb_channels(channel_layout) == channels) {
        return channel_layout;
    } else {
        return 0;
    }
}

// decode a new packet(not multi-frame)
// return decoded frame size, not decoded packet size
int audio_decode_frame(uint8_t *audio_buf, int buf_size) {
    static AVPacket pkt;
    static uint8_t *audio_pkt_data = NULL;
    static int audio_pkt_size = 0;
    int len1, data_size;
    int got_frame;
    AVFrame * frame = NULL;
    static int reconfigure = 1;
    int ret = -1;

    for (;;) {

        while (audio_pkt_size > 0) {

            if (NULL == frame) {
                frame = av_frame_alloc();
            }

            data_size = buf_size;
            got_frame = 0;

            // len1 is decoded packet size
            len1 = avcodec_decode_audio4(global_context.acodec_ctx, frame,
                    &got_frame, &pkt);
            if (got_frame) {

                if (reconfigure) {

                    reconfigure = 0;
                    int64_t dec_channel_layout = get_valid_channel_layout(
                            frame->channel_layout,
                            av_frame_get_channels(frame));

                    // used by init_filter_graph()
                    audio_filter_src.fmt = (enum AVSampleFormat) frame->format;
                    audio_filter_src.channels = av_frame_get_channels(frame);
                    audio_filter_src.channel_layout = dec_channel_layout;
                    audio_filter_src.freq = frame->sample_rate;

                    init_filter_graph(&agraph, &in_audio_filter,
                            &out_audio_filter);
                }

                if ((ret = av_buffersrc_add_frame(in_audio_filter, frame))
                        < 0) {
                    av_log(NULL, AV_LOG_ERROR,
                            "av_buffersrc_add_frame :  failure. \n");
                    return ret;
                }

                if ((ret = av_buffersink_get_frame(out_audio_filter, frame))
                        < 0) {
                    av_log(NULL, AV_LOG_ERROR,
                            "av_buffersink_get_frame :  failure. \n");
                    continue;
                }

                data_size = av_samples_get_buffer_size(NULL, frame->channels,
                        frame->nb_samples, (enum AVSampleFormat) frame->format,
                        1);

                // len1 is decoded packet size
                // < 0  means failure or error,so break to get a new packet
                if (len1 < 0) {
                    audio_pkt_size = 0;
                    av_log(NULL, AV_LOG_ERROR,
                            "avcodec_decode_audio4 failure. \n");
                    break;
                }

                // decoded data to audio buf
                memcpy(audio_buf, frame->data[0], data_size);

                audio_pkt_data += len1;
                audio_pkt_size -= len1;

                int n = 2 * global_context.acodec_ctx->channels;
                /*audio_clock += (double) data_size
                 / (double) (n * global_context.acodec_ctx->sample_rate); // add bytes offset */
                av_free_packet(&pkt);
                av_frame_free(&frame);

                return data_size;
            } else if (len1 < 0) {
                char errbuf[64];
                av_strerror(ret, errbuf, 64);
                LOGV2("avcodec_decode_audio4 ret < 0, %s", errbuf);
            }
        }

        av_free_packet(&pkt);
        av_frame_free(&frame);

        // get a new packet
        if (packet_queue_get(&global_context.audio_queue, &pkt) < 0) {
            return -1;
        }

        //LOGV2("pkt.size is %d", pkt.size);

        audio_pkt_data = pkt.data;
        audio_pkt_size = pkt.size;
    }

    return ret;
}

GitHub源码

请参考完整的源码路径:
https://github.com/ericbars/FFmpegOpenSLES

基于ffmpeg+opengl+opensl es的android视频播放器 最近做了一个android视频播放器,在jni中采用c/c++现了播放器的播放,暂停,快进等基本的播放器功能. 使用开源库FFMpeg来解码,得到音视频数据,FFMPEG是一个功能强大的音视频解码,编码的库,当然了,若要在android中使用ffmpeg库,必须要交叉编译,才能得到arm平台才能运行的.so文件. 在ffmpeg的官网https://ffmpeg.org/中下载最新的ffmpe 阅读详情

相关推荐

Android FFmpeg开发(三),利用OpenSL ES实现音频渲染

本文利用FFmepg+OpenSL ES实现音频流解码和播放,至此一款简单的视频播放器就已经做出来了。源码链接# 检出 v3 tag 进行查看Android FFmpeg开发(三),利用OpenSL ES实现音频渲染★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。见下方!

yinshipin007的博客 318

基于NDK、C++、FFmpeg的android视频播放器开发实战

课程包含了对流媒体(拉流)的播放,演示了播放rtmp的香港卫视,支持rtsp摄像头和http网络视频的播放,支持访问本地的视频文件,并精确显示和控制播放进度:1讲解如何编译Android平台的ffmpeg库,使其支持neon技术和硬解码,并测试性能2使用opengles的NDK shader高效播放yuv视频,不耗费cpu性能更优(GPU)3代码支持硬解码(省电不耗cpu)和多线程解码(高性能每秒解码240帧1080p)4支持网络流媒体(rtmp,rtsp,http)可直接拉流播放电视并支持rtsp摄像头访问5课程将设计模式应用到实践 - 观察者,构建者,门面,代理,适配器,单件模式

Android-音视频学习系列-(十)基于-FFmpeg-+-OpenSLES-实现音频万能播放器

粗略架构组成: kotlin: Kotlin 语言是由 JetBrains 公司开发,2010 面世,2017 年正式在谷歌 I/O 大会上推荐 Kotlin 作为 Android 开发语言。 FFmpeg: FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。 SoundTouch: 可以在 PCM 音频裸流基础上对音频变调变速 OpenSLES: OpenSL ES(嵌入式系统的开放声音库.

m0_64604042的博客 1124

ffmpeg编程-音量调节

ffmpeg编程,实现音量调节功能

occupy8的专栏 2630

Android平台C++媒体播放器开发:从FFmpeg解码到音视频同步实战

在移动应用开发中,高性能音视频处理是核心技术领域之一,其底层实现通常依赖于C/C++语言以保证执行效率。其核心原理涉及媒体数据的解封装、解码、渲染与同步流程,通过FFmpeg等开源库实现跨格式编解码,结合多线程架构管理数据流。这项技术的工程价值在于能够为短视频、直播、游戏等场景提供稳定高效的播放能力,解决音画同步、低延迟等关键问题。本文以Android平台为例,详细解析如何利用NDK、OpenGL ES和OpenSL ES构建本地播放器,涵盖FFmpeg集成、线程模型设计及音视频同步策略,帮助开发者深入理解

weixin_30918633的博客 416

FFmpeg与OpenSL ES:Android万能音频播放器实现指南

FFmpeg是一个非常强大的多媒体框架,提供了录制、转换和流化音频和视频的广泛功能。它是开源项目,广泛应用于音视频编解码、格式转换、流处理等领域。在移动设备上,FFmpeg的性能和功能同样强大,因此被广泛应用于Android平台。至此,我们已经完成了FFmpeg源码的获取以及Android NDK环境的配置。这为后续编译FFmpeg和将其集成到Android应用中奠定了基础。

weixin_30951515的博客 955

android音视频开发基础6 Android OpenSL ES

OpenSL ES (Open Sound Library for Embedded Systems)是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台部署,降低执行难度,促进高级音频市场的发展。简单来说OpenSL ES是一个嵌入式跨平台免费的音...

DTJ_74的专栏 822

Android音频播放:FFmpeg与OpenSL ES解码与播放实践

在移动开发的世界里,音频播放是应用中不可或缺的一环,尤其是在多媒体应用、游戏和各种服务中。Android提供了强大的音频处理和播放能力,从底层的Linux音频框架到高级的API,例如MediaPlayer和AudioTrack,开发者可以利用这些工具来实现音频的播放、录制和处理。不过,真正实现一个高质量的音频应用并非易事,需要深入了解Android音频播放架构。通过本章的介绍,我们了解到FFmpeg音频解码库的基础知识以及如何在Android平台上集成和应用。

weixin_42527665的博客 1249

Android音视频学习系列(十) — 基于FFmpeg + OpenSL ES实现音频万能播放器

说实话现在搞 Android 开发的要求是越来越高,初级都要被淘汰的感觉。所以想要一直往 Android 的道路上走,深入音视频领域是一个不错的选择。虽然现在跨平台Flutter很火,但终究它还是一个写 UI 的(个人看法)。程序员随着年龄的增长不可能一直写 UI。但是深入C/C++就不一样了。它可以给移动端甚至Flutter提供底层 SDK 和技术支持这难道不香吗?所以想要或者准备学习音视频知识的,那么该篇文章可以助你快速入门。自己写的简单音频处理库,目前包含如下功能:功能是否完成。

金戈鐡馬 869

Spring Boot整合AWS S3文件上传实战:从配置到权限管理全流程解析

本文详细解析了Spring Boot整合AWS S3文件上传的全流程,包括基础配置、核心上传功能实现、高级权限控制及安全最佳实践。通过实战案例展示如何高效处理文件上传、大文件分片及权限管理,帮助开发者构建健壮的文件管理系统。特别强调AWS S3的高可用性和安全性,适用于企业级应用开发。

sam99的博客 283

MIT Mini Cheetah--仿真流程

MIT Mini Cheetah–仿真 摘要 由于官方提供代码中,控制仿真环境中的猎豹机器人运动需要遥控器来控制。官方默认推荐使用logitech Gamepad F310游戏手柄:We use the Logitech F310 controller. There’s a switch in the back, which should be in the “X” position. The controller needs to reconnected if you change the switch

Galaxy_Robot的博客 5916

音视频系列四 ffmpeg配合opensl es播放音频

文章目录一 解码成PCM二 opensl es创建引擎,混音器 一 解码成PCM 流程其实和视频差不多,参考播放视频的代码,稍作增改 先定义后面需要用到的全局变量 //音频解码上下文 AVCodecContext *audioCodecContext; //音频重采样上下文 SwrContext *audioSwrContext; //音频重采样缓冲区 uint8_t *audioBuffer; //音频重采样声道 uint64_t outChLayout = AV_CH_LAYOUT_STEREO; //

Welcome_Word的博客 374

汽车售后诊断 ODX 和 OTX 对比分析报告

在汽车行业不断发展的当下,汽车售后诊断技术对于保障车辆性能、维护车主权益以及提升汽车品牌服务质量起着至关重要的作用。随着汽车电子化程度的不断提高,售后诊断所涉及的数据和流程愈发复杂,这就促使行业需要更加标准化、高效化的诊断技术方案。开放诊断数据交换(ODX)和开放测试序列交换(OTX)便是在这样的背景下应运而生的两种重要标准,它们为汽车售后诊断提供了强大的支持。本报告将深入剖析 ODX 和 OTX 的技术细节、应用场景、优势与挑战,并进行全面的对比,旨在为汽车行业从业者在选择和应用这两种标准时提供有价值的参

芊言凝语 1638

学习笔记(01):FFmpeg打造Android万能音频播放器-OpenSL ES介绍并实现播放PCM数据

FFmpeg打造Android万能音频播放器视频教程,移植FFmpeg到Android平台,利用FFmpeg解码音频数据并用OpenSL ES来播放,打造Android万能播放器。

GongHao2014的专栏 612

手把手教你用STM32F103C8T6驱动CLRC663国产NFC模块(兼容A/B/F/15693卡)

本文详细介绍了如何使用STM32F103C8T6驱动国产CLRC663 NFC模块,支持ISO14443A/B、Felica和ISO15693协议。从硬件设计、协议适配到状态监控,提供完整的工程实现方案,适用于智能门锁、支付终端等应用场景。

weixin_30689307的博客 445

关于cefsharp访问iqiyi.com显示403 Forbidden解决办法(2种方法)

cefsharp不加UserAgent的默认值为 (注意126是当前cefsharp的版本)Edge浏览器的UserAgent 为 (edge的版本是128的)说明根据浏览器的不同,UserAgent可能不太一样。

无线智能模块 可视DIY智能专家 1319

LearningVideo与OpenSL ES:实现高质量音频播放的完整方案

LearningVideo是【Android 音视频开发打怪升级】系列文章的示例代码项目,它从MediaCodec硬解、FFmpeg软解、OpenGL等多个方面,全方位讲解如何在Android上进行音视频编辑开发。本文将聚焦于LearningVideo中结合OpenSL ES实现高质量音频播放的完整方案,帮助开发者掌握Android平台下音频播放的核心技术。 [![Android音视频开发教程封

gitblog_01087的博客 701

无人机视觉锁定与目标跟踪技术深度解析(含完整代码)

本文介绍了无人机视觉锁定系统的核心技术实现,包括系统架构、目标检测与跟踪算法。系统采用YOLOv5进行初始目标检测,结合手动框选作为备选方案。在跟踪环节对比了KCF、CSRT等传统方法和SiamFC等深度学习算法,最终选用CSRT作为CPU平台最优方案。文章详细解析了相关滤波跟踪的数学原理,并提供了目标检测器和跟踪器的Python实现代码。该系统能有效处理目标遮挡、光照变化等挑战,为无人机自主跟踪提供了实用解决方案。

电脑管家 686

android ffmpeg+OpenGL播放yuv+openSL 快放 慢放 视频播放器

这里是完整的音视频播放器,功能如下(这里有iOS版的): 视频是通过opengl 播放yuv数据,音频是opensl播放。 app运行流程如下图: 红色虚线内的是一个线程的运行,总共涉及到四个线程。 java层几乎没有播放器的内容,ffmpeg,opengl,opensl都是在c++层运行,所以可以原封不动的迁移到其他平台,如ios,不过c/c++代码ios程序员看起来不太友好,有

huizai的博客 5509

C#上位机面试必问30题:机器视觉+工业通信+性能优化(含答案解析)

工业通信:围绕“稳定性”展开,重点答异常处理、断线重连、资源释放;机器视觉:围绕“精度+速度”展开,重点答预处理、ROI、参数优化;性能优化:围绕“UI不卡、内存不泄、效率不低”展开,重点答异步、限流、资源复用;面试加分项:结合实际项目案例(如“我在XX项目中用信号量解决了多设备并发卡顿问题”)。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1509

GD32F4xx 移植agile_modbus软件包与电能表通信

GD32F4XX 移植agile modbus软件包实现与DTSU电能表通信调试记录

madao1234的博客 1305

android ffmpeg音频,Android 音视频深入 十四 FFmpeg与OpenSL ES 播放mp3音乐,能暂停(附源码...

这次说的是FFmpeg解码mp3,数据给OpenSL ES播放,并且能够暂停。1.创建引擎slCreateEngine(&engineObject,0,NULL,0,NULL,NULL);//创建引擎(*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE);//实现engineObject接口对象(*engineObject)-&...

weixin_34434948的博客 224

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Android OpenSL ES 音频播放器

OpenSL ES (Open Sound Library for Embedded Systems) 是一个跨平台、无授权费用的音频处理库,它为移动多媒体设备的应用开发者提供了标准化、高性能、低响应的音频功能实现方法,统一的 API 使得通过 OpenSL ES 开发的音频处理程序能够很好的在多个平台上运行。

irainsa的博客 289

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

FFmpeg从入门到入魔(4):OpenSL ES播放PCM音频

虽然OpenSL ES是基于C语言开发,但是它的实现思想是基于面向对象的,与FFmpeg框架提供一系列的函数接口不同,OpenSL ES是以Interface的方式来提供API,即除了slCreateEngine这个函数用来创建声音引擎对象接口之外,其他所有的操作都是通过调用接口的成员函数完成的,这与JNI非常相似。4.停止解码,释放资源。Object和Interface是OpenSL ES库的两个非常重要的概念,OpenSL ES的整个框架就是基于这两个概念构成的,后续对该库的使用也是基于此来实现。

irainsa的博客 1390

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

上一篇: 通过FFmpeg解码和OpenGL的YUV转RGB实现Android视频播放
下一篇: 基于FFmpeg和Android的音视频同步播放实现
ericbars
博客等级 码龄10年 120粉丝 30原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值