Android Opensles 使用 FFmpeg 重采样 PCM

标签 android ffmpeg pcm opensl

我正在尝试在 android 中使用 ffmpeg 和 opensles 播放音频流。问题似乎出在将解码和重采样的帧从 ffmpeg 传递到 opensles 时,因为我听到的声音听起来很机械并且有刮痕。

来自 ffmpeg 的解码帧:

PCM
48000 Hz
S16p

Opensles 在这种情况下需要:

PCM
48000 Hz
S16

Opensles 设置:

SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 255};

SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 2 , SL_SAMPLINGRATE_48, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
                SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN};

SLDataSource audioSrc = {&loc_bufq, &format_pcm};

这是重新采样和入队到 opensles 的伪代码:

#define OPENSLES_BUFLEN 10
#define MAX_AUDIO_FRAME_SIZE 192000

DECLARE_ALIGNED(16,uint8_t,audio_buffer)[MAX_AUDIO_FRAME_SIZE * OPENSLES_BUFLEN];


int decode_audio(AVCodecContext * ctx, SwrContext *swr_context, AVPacket *packet, AVFrame * frame){
    int got_frame_ptr;
    int len = avcodec_decode_audio4(ctx, frame, &got_frame_ptr, packet);

    if(!got_frame_ptr)
        return -ERROR;

    int original_data_size = av_samples_get_buffer_size(NULL, ctx->channels,
        frame->nb_samples, ctx->sample_fmt, 1);

    uint8_t *audio_buf;
    int data_size;

    if (swr_context != NULL) {
        uint8_t *out[] = { audio_buffer };

        int sample_per_buffer_divider = 2* av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);;


        int len2 = swr_convert(swr_context, out,
            sizeof(audio_buffer) / sample_per_buffer_divider,
            frame->extended_data, frame->nb_samples);



        if (len2 < 0) {
            return -ERROR;
        }
        if (len2 == sizeof(audio_buffer) / sample_per_buffer_divider) {
            swr_init(swr_context);
        }
        audio_buf = audio_buffer;
        data_size = len2 * sample_per_buffer_divider;
        } 
     else {
        audio_buf = frame->data[0];
        data_size = original_data_size;
     }

     (*opengSLESData->bqPlayerBufferQueue)->Enqueue(opengSLESData->bqPlayerBufferQueue, audio_buf, data_size)


}

如果有任何帮助,我将不胜感激。

最佳答案

例子可能有帮助

#include "stdafx.h"
#include <iostream>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
//#include "swscale.h"
#include "libswresample/swresample.h"
};

FILE           *fin,    *fout;

int ffmpeg_audio_decode( const char * inFile, const char * outFile)
{
// Initialize FFmpeg
av_register_all();

AVFrame* frame = avcodec_alloc_frame();
if (!frame)
{
    std::cout << "Error allocating the frame" << std::endl;
    return 1;
}

// you can change the file name "01 Push Me to the Floor.wav" to whatever the file is you're reading, like "myFile.ogg" or
// "someFile.webm" and this should still work
AVFormatContext* formatContext = NULL;
//if (avformat_open_input(&formatContext, "01 Push Me to the Floor.wav", NULL, NULL) != 0)
if (avformat_open_input(&formatContext, inFile, NULL, NULL) != 0)
{
    av_free(frame);
    std::cout << "Error opening the file" << std::endl;
    return 1;
}

if (avformat_find_stream_info(formatContext, NULL) < 0)
{
    av_free(frame);
    av_close_input_file(formatContext);
    std::cout << "Error finding the stream info" << std::endl;
    return 1;
}

AVStream* audioStream = NULL;
// Find the audio stream (some container files can have multiple streams in them)
for (unsigned int i = 0; i < formatContext->nb_streams; ++i)
{
    if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
    {
        audioStream = formatContext->streams[i];
        break;
    }
}

if (audioStream == NULL)
{
    av_free(frame);
    av_close_input_file(formatContext);
    std::cout << "Could not find any audio stream in the file" << std::endl;
    return 1;
}

AVCodecContext* codecContext = audioStream->codec;

codecContext->codec = avcodec_find_decoder(codecContext->codec_id);
if (codecContext->codec == NULL)
{
    av_free(frame);
    av_close_input_file(formatContext);
    std::cout << "Couldn't find a proper decoder" << std::endl;
    return 1;
}
else if (avcodec_open2(codecContext, codecContext->codec, NULL) != 0)
{
    av_free(frame);
    av_close_input_file(formatContext);
    std::cout << "Couldn't open the context with the decoder" << std::endl;
    return 1;
}

std::cout << "This stream has " << codecContext->channels << " channels and a sample rate of " << codecContext->sample_rate << "Hz" << std::endl;
std::cout << "The data is in the format " << av_get_sample_fmt_name(codecContext->sample_fmt) << std::endl;

//codecContext->sample_fmt = AV_SAMPLE_FMT_S16;

int64_t outChannelLayout = AV_CH_LAYOUT_MONO; //AV_CH_LAYOUT_STEREO;
AVSampleFormat outSampleFormat = AV_SAMPLE_FMT_S16; // Packed audio, non-planar (this is the most common format, and probably what you want; also, WAV needs it)
int outSampleRate = 8000;//44100;
// Note that AVCodecContext::channel_layout may or may not be set by libavcodec. Because of this,
// we won't use it, and will instead try to guess the layout from the number of channels.
SwrContext* swrContext = swr_alloc_set_opts(NULL,
    outChannelLayout,
    outSampleFormat,
    outSampleRate,
    av_get_default_channel_layout(codecContext->channels),
    codecContext->sample_fmt,
    codecContext->sample_rate,
    0,
    NULL);

if (swrContext == NULL)
{
    av_free(frame);
    avcodec_close(codecContext);
    avformat_close_input(&formatContext);
    std::cout << "Couldn't create the SwrContext" << std::endl;
    return 1;
}

if (swr_init(swrContext) != 0)
{
    av_free(frame);
    avcodec_close(codecContext);
    avformat_close_input(&formatContext);
    swr_free(&swrContext);
    std::cout << "Couldn't initialize the SwrContext" << std::endl;
    return 1;
}

fout = fopen(outFile, "wb+");

AVPacket packet;
av_init_packet(&packet);

// Read the packets in a loop
while (av_read_frame(formatContext, &packet) == 0)
{
    if (packet.stream_index == audioStream->index)
    {
        AVPacket decodingPacket = packet;

        while (decodingPacket.size > 0)
        {
            // Try to decode the packet into a frame
            int frameFinished = 0;
            int result = avcodec_decode_audio4(
                codecContext, 
                frame, 
                &frameFinished, 
                &decodingPacket);

            if (result < 0 || frameFinished == 0)
            {
                break;
            }

            unsigned char buffer[100000] = {NULL};
            unsigned char* pointers[SWR_CH_MAX] = {NULL};
            pointers[0] = &buffer[0];

            int numSamplesOut = swr_convert(
                swrContext,
                pointers,
                outSampleRate,
                (const unsigned char**)frame->extended_data,
                frame->nb_samples);


            fwrite(  
                (short *)buffer, 
                sizeof(short), 
                (size_t)numSamplesOut, 
                fout);

            decodingPacket.size -= result;
            decodingPacket.data += result;
        }

    }

    // You *must* call av_free_packet() after each call to av_read_frame() or else you'll leak memory
    av_free_packet(&packet);
}

// Some codecs will cause frames to be buffered up in the decoding process. If the CODEC_CAP_DELAY flag
// is set, there can be buffered up frames that need to be flushed, so we'll do that
if (codecContext->codec->capabilities & CODEC_CAP_DELAY)
{
    av_init_packet(&packet);
    // Decode all the remaining frames in the buffer, until the end is reached
    int frameFinished = 0;
    while (avcodec_decode_audio4(codecContext, frame, &frameFinished, &packet) >= 0 && frameFinished)
    {
    }
}

// Clean up!
av_free(frame);
avcodec_close(codecContext);
av_close_input_file(formatContext);
fclose(fout);
}

关于Android Opensles 使用 FFmpeg 重采样 PCM,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19066162/

相关文章:

python - 通过 ffmpeg 发布时的视频色差

node.js - 使用 fluent-ffmpeg 处理视频输出文件时长减半

audio - 复用到 MPEG-TS : wrong parameters for audio stream

android - 我的build.gradle not compatible with error in line 22

android - 在 Android 中重新抛出异常

google-app-engine - 使用 ffmpeg 转码 GCS 文件

android - 如何将 .pcm 文件转换为 .wav 或 .mp3?

flash - 是否可以以8KHz采样数据速率而不是44.1KHz获得声音原始数据?

android - 使用XMPP在android中用户离线时向用户发送通知

android - 在回收站 View 中只选择一个单选按钮