NDK 与 FFmpeg相关问题

1、ffmpeg 播放音视频代码流程

第一步 、 解封装

1、avformat_alloc_context初始化一个AVFormatContext 结构体
2、avformat_open_input(&avFormatContext, data_source, 0, &dictionary) 打开一个媒体源
3、avformat_find_stream_info(avFormatContext, 0)找到媒体流的信息, 至此媒体文件的内容被封装到了AVFormatContext 结构体中了;
4、avFormatContext->nb_streams 开启遍历,nb_streams代表AVFormatContext结构中的流的个数,一般有视频流、音频流 和字幕流
5、avFormatContext->streams[index_stream]获取到AVFormatContext结构体中的一个流
6、avcodec_find_decoder(avCodecParameters->codec_id) 通过编码器id找到对应的流的编解码器AVCodec
7、avcodec_alloc_context3(avCodec) 通过一个编解码器初始化出一个AVCodecContext
8、avcodec_parameters_to_context(avCodecContext, avCodecParameters)往编解码器中设置流的参数
9、avcodec_open2(avCodecContext, avCodec, 0) 打开编解码器,此时这个流的编解码器和编解码器上下文才可以真正使用了。

第二步 、 音视频解码

音视频解码的逻辑是每个对应的流中(音频流 或者视频流)定义两个队列(未解码的AVPacket 和 已经解码的AVFrame)

1、av_packet_alloc()初始化一个AVPacket,用来存放一个从编解码器上下文中读出来的一帧帧数据
2、av_read_frame(avFormatContext, avPacket) 从编解码器上下文中读一帧数据到AVPacket
3、packages.push(avPacket) 加入到队列

4、packages.pop(avPacket) 从队列中取出一个数据
5、AVFrame *avFrame = av_frame_alloc() 初始化一个AVFrame结构体
6、avcodec_receive_frame(avCodecContext, avFrame) 通过编解码器上下文中解码出一帧数据放到avFrame结构体中
7、frames.push(avFrame);解码完成以后放入队列

其中123步在一个线程中执行,4567步在另一个线程中执行

第三步 视频的渲染

1、sws_getContext()初始化一个SwsContext结构体,参数解释 第123个参数分别是被转换源的宽高和格式第456个参数转换后的宽高和格式 第7个参数是转换所使用的算法 剩下三个参数全部设置成NULL
2、av_image_alloc(dst_data, dst_linesize, pContext->width, pContext->height, AV_PIX_FMT_ARGB, 1)给图像申请内存,dst_data代表图像通道、dst_linesize代表图像每个通道的内存对齐的步长,即一行的对齐内存的宽度。
3、frames.pop(frame)从已解码的队列中取出一帧图像
4、sws_scale(sws_ctx, frame->data, frame->linesize, 0, pContext->height, dst_data, dst_linesize) 讲解码的原始数据转换成yuv数据
5、渲染到屏幕上

基于SurfaceView+ANativeWindow 渲染到屏幕

https://blog.csdn.net/byhook/article/details/84027283
1、ANativeWindow_fromSurface(env, surface)获取到ANativeWindow 结构体
2、ANativeWindow_setBuffersGeometry(nativeWindow, width, height , WINDOW_FORMAT_RGBA_8888);设置窗口属性
3、ANativeWindow_lock(nativeWindow, &windowBuffer, 0) 读取到窗口buffer数据
4、修改数据

// 填数据到buffer,其实就是修改数据
uint8_t * dst_data = static_cast<uint8_t *>(windowBuffer.bits);
int lineSize = windowBuffer.stride * 4; // RGBA
// 下面就是逐行Copy了
for (int i = 0; i < windowBuffer.height; ++i) {
    // 一行Copy
    memcpy(dst_data + i * lineSize, src_data + i * src_liinesize, lineSize);
}

第四步 音频的渲染

1、创建引擎并获取引擎接口

1、slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
2、(*engineObject) ->Realize(engineObject, SL_BOOLEAN_FALSE)
3、(*engineObject) ->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface)

2、创建混音器

1、(*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
2、(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE)
3、(*outputMixObject)->GetInterface(outputMixObject,SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb)
4、设置

const SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_DEFAULT;
(*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &settings);

3、创建播放器

1、配置输入声音信息

    // 创建buffer缓冲类型的队列 2个队列
    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                                                       2};
    // pcm数据格式
    // SL_DATAFORMAT_PCM:数据格式为pcm格式
    // 2:双声道
    // SL_SAMPLINGRATE_44_1:采样率为44100(44.1赫兹 应用最广的,兼容性最好的)
    // SL_PCMSAMPLEFORMAT_FIXED_16:采样格式为16bit (16位)(2个字节)
    // SL_PCMSAMPLEFORMAT_FIXED_16:数据大小为16bit (16位)(2个字节)
    // SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT:左右声道(双声道)  (双声道 立体声的效果)
    // SL_BYTEORDER_LITTLEENDIAN:小端模式
    SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1,
                                   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};

2、配置音轨(输出

扫描二维码关注公众号,回复: 9639632 查看本文章
    // 设置混音器
    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};

3、创建播放器 CreateAudioPlayer(engineInterface, &bqPlayerObject, &audioSrc, &audioSnk, 1, ids, req);
4、初始化播放器 (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE)
5、获取播放器接口 (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay)
6、设置播放回调函数

    //  获取播放器队列接口:SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);

    //  设置回调 
    void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
    (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);

7、设置播放器状态为播放状态 并手动激活回调函数

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

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

8、回调函数编写(将pcm原始数据重采样)
1、定义分配重采样上下文

    out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); // 通道数   AV_CH_LAYOUT_STEREO(双声道的意思)
    out_sample_size = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
    out_sample_rate = 44100; // 采样率
    out_buffers_size = out_sample_rate * out_sample_size * out_channels;

    out_buffers = static_cast<uint8_t *>(malloc(out_buffers_size));
    memset(out_buffers, 0, out_buffers_size);

    // swr_ctx = swr_alloc();
    swr_ctx = swr_alloc_set_opts(0, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, out_sample_rate,
              pContext->channel_layout, pContext->sample_fmt, pContext->sample_rate, 0 ,0);

    swr_init(swr_ctx);

2、计算PCM数据大小

int AudioChannel::getPCM() {
    int pcm_data_size = 0;

    // PCM 在 frames 队列中
    AVFrame * frame = 0;
    while(isPlaying) {
        int ret = frames.pop(frame);

        // 如果停止播放,跳出循环, 出了循环,就要释放
        if (!isPlaying) {
            break;
        }

        if (!ret) {
            continue;
        }

        // PCM 的处理逻辑
        frame->data;

        // 音频播放器的数据格式是我们在下面定义的(16位 双声道 ....)
        // 而原始数据(是待播放的音频PCM数据)
        // 所以,上面的两句话,无法统一,一个是(自己定义的16位 双声道 ..) 一个是原始数据,为了解决上面的问题,就需要重采样。
        // 开始重采样
        int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, frame->sample_rate) +
                                        frame->nb_samples, out_sample_rate, frame->sample_rate, AV_ROUND_UP);

        ret = swr_convert(swr_ctx, &out_buffers, dst_nb_samples, (const uint8_t **) frame->data, frame->nb_samples);
        if (ret < 0) {
            fprintf(stderr, "Error while converting\n");
        }

        // pcm_data_size = ret * out_sample_size; // 每个声道的数据
        pcm_data_size = ret * out_sample_size * out_channels;

        break;
    } // end while
    releaseAVFrame(&frame); // 渲染完了,frame没用了,释放掉
    return pcm_data_size;
}

3、播放数据(*bq)->Enqueue(bq, audioChannel->out_buffers , pcmSize);

发布了58 篇原创文章 · 获赞 16 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/dingshuhong_/article/details/104142195
ndk