FFmpeg实现Android音视频播放器【1】

FFmpeg实现Android音视频播放器【1】

1、音视频数据解封包

这一步主要是将视频文件中的视频和音频数据进行拆分。首先获取AVFormatContext对象,通过AVFormatContext打开音视频数据源(.mp4文件或者音视频流)

formatContext = avformat_alloc_context();
int r = avformat_open_input(&formatContext, data_source, nullptr, &dictionary);

2、查找媒体中的音视频流的信息

解封包成功后,formatContext中会有多条数据流,循环遍历数据流,获取音频和视频信息。最后获取解码器AVCodec,将音视频文件和解码器分别传给video和audio子线程处理

AVStream *stream = formatContext->streams[stream_index];
AVCodecParameters *parameters = stream->codecpar;
AVCodec *codec = avcodec_find_decoder(parameters->codec_id);
codecContext = avcodec_alloc_context3(codec);
r = avcodec_parameters_to_context(codecContext, parameters);
r = avcodec_open2(codecContext, codec, nullptr);
 if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
   // 是音频
   audio_channel = new AudioChannel(stream_index, codecContext, time_base);
    } else if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO)         {
     // 是视频
    video_channel = new VideoChannel(stream_index, codecContext, time_base, fps);
    }

3、视频流处理流程

前面步骤获取的视频信息,还属于压缩数据,需要在这一步,进行解码,得到yuv格式数据,然后再将yuv格式转换成rgba格式数据才能播放。所以这里采用的双队列数据结构处理,线程1往pkt队列放入压缩数据,线程2从pkt队列中获取压缩数据进行解码,然后放入frame队列中,线程3从fram队列中获取yuv数据将其转换成rgba数据

//线程1将压缩数据插入pkt队列
video_channel->packets.insertToQueue(packet);
//线程2从pkt队列中获取压缩数据
int ret = packets.getQueueAndDel(pkt); 
//将pkt数据解码为frame(此时为yuv格式)
ret = avcodec_send_packet(codecContext, pkt);
ret = avcodec_receive_frame(codecContext, frame);
frames.insertToQueue(frame);

//将yuv格式转rgba格式
sws_getContext(
            codecContext->width,
            codecContext->height,
            codecContext->pix_fmt, 


            codecContext->width,
            codecContext->height,
            AV_PIX_FMT_RGBA,
            SWS_BILINEAR, NULL, NULL, NULL);
 sws_scale(sws_ctx,

                  frame->data, frame->linesize,
                  0, codecContext->height,


                  dst_data,
                  dst_linesize
        );

4、视频播放

视频播放在android端主要是通过nativewindow来实现,我们可以简单的理解为,我们只需要将一帧一帧的frame交给nativewindow就可以了,这就是在播放。具体流程如下:

//创建ANativeWindow,通过jni调用,将android端的surface传递给C++层
window = ANativeWindow_fromSurface(env, surface);
// 设置窗口的大小,各个属性
 ANativeWindow_setBuffersGeometry(window, width, height, WINDOW_FORMAT_RGBA_8888);
  // 设置缓冲区 buffer
    ANativeWindow_Buffer window_buffer; //
    ANativeWindow_lock(window, &window_buffer, 0)
  //将数据复制到buffer,即为播放
   uint8_t *dst_data = static_cast<uint8_t *>(window_buffer.bits);
   int dst_linesize = window_buffer.stride * 4;
   for (int i = 0; i < window_buffer.height; ++i) { 
        memcpy(dst_data + i * dst_linesize, src_data + i * src_lineSize, dst_linesize); }

5、音频流处理

首先同样的,音频的压缩数据也需要先进行解码生成pcm格式的原始数据,然后在对pcm数据进行重采样。我们知道音频数据三要素:采样率,位声,声道数。重采样就是将这三要素与播放设备的三要素保持一致,否则会出现电流声

// 初始化 重采样上下文SwrContext
out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); // STEREO:双声
    out_sample_size = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); // 每个sample是16 bit == 2字节
    out_sample_rate = 44100; 

    // out_buffers_size = 176,400
    out_buffers_size = out_sample_rate * out_sample_size * out_channels; // 44100 * 2 * 2 = 176,400
    out_buffers = static_cast<uint8_t *>(malloc(out_buffers_size)); 


    swr_ctx  = swr_alloc_set_opts(0,

                                  AV_CH_LAYOUT_STEREO,  
                                  AV_SAMPLE_FMT_S16,  
                                  out_sample_rate, 


                                  codecContext->channel_layout, 
                                  codecContext->sample_fmt, 
                                  codecContext->sample_rate, 
                                  0, 0);
    swr_init(swr_ctx);


    //  重采样
      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); 


        int samples_per_channel = swr_convert(swr_ctx,

                                              &out_buffers, 
                                              dst_nb_samples, 


                                              (const uint8_t **) frame->data, 
                                              frame->nb_samples); 

6、音频流播放

音频播放过程较为复杂,需要初始化引擎,设置混音器,设置播放器等操作,但都是固定代码,具体流程如下

 SLresult result; // 用于接收 执行成功或者失败的返回值

    /**
     * TODO 1.创建引擎对象并获取【引擎接口】
     */

    // 1.1 创建引擎对象:SLObjectItf engineObject
    result = slCreateEngine(&engineObject, 0, 0, 0, 0, 0);
    if (SL_RESULT_SUCCESS != result) {
        LOGE("创建引擎 slCreateEngine error");
        return;
    }

    // 1.2 初始化引擎
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); // SL_BOOLEAN_FALSE:延时等待你创建成功
    if (SL_RESULT_SUCCESS != result) {
        LOGE("创建引擎 Realize error");
        return;
    }

    // 1.3 获取引擎接口
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);
    if (SL_RESULT_SUCCESS != result) {
        LOGE("创建引擎接口 Realize error");
        return;
    }


    if (engineInterface) {
        LOGD("创建引擎接口 create success");
    } else {
        LOGD("创建引擎接口 create error");
        return;
    }

    /**
     * TODO 2.设置混音器
     */
     // 2.1 创建混音器
    result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0); 
    if (SL_RESULT_SUCCESS != result) {
        LOGD("初始化混音器 CreateOutputMix failed");
        return;
    }

    // 2.2 初始化混音器
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); // SL_BOOLEAN_FALSE:延时等待你创建成功
    if (SL_RESULT_SUCCESS != result) {
        LOGD("初始化混音器 (*outputMixObject)->Realize failed");
        return;
    }

    LOGI("2、设置混音器 Success");

    /**
     * TODO 3.创建播放器
     */
     // 创建buffer缓存类型的队列  2的队列大小
    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                                                       10};


    SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, // 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}; 
    // 设置混音器
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; // SL_DATALOCATOR_OUTPUTMIX:输出混音器类型
    SLDataSink audioSnk = {&loc_outmix, NULL}; // 
    const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};

    //  创建播放器 SLObjectItf bqPlayerObject
    result = (*engineInterface)->CreateAudioPlayer(engineInterface, 
                                                   &bqPlayerObject, 
                                                   &audioSrc, 
                                                   &audioSnk, 

                                                   // 
                                                   1, 
                                                   ids,  
                                                   req 
    );

    if (SL_RESULT_SUCCESS != result) {
        LOGD("创建播放器 CreateAudioPlayer failed!");
        return;
    }

    // 3.4 初始化播放器:SLObjectItf bqPlayerObject
    result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);  // SL_BOOLEAN_FALSE:延时等待你创建成功
    if (SL_RESULT_SUCCESS != result) {
        LOGD("实例化播放器 CreateAudioPlayer failed!");
        return;
    }
    LOGD("创建播放器 CreateAudioPlayer success!");

    // 3.5 获取播放器接口 
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); // SL_IID_PLAY:播放接口 == iplayer
    if (SL_RESULT_SUCCESS != result) {
        LOGD("获取播放接口 GetInterface SL_IID_PLAY failed!");
        return;
    }
    LOGI("3、创建播放器 Success");
     /**
     * TODO 4.设置回调函数
     */
    // 4.1 获取播放器队列接口:SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue  // 播放需要的队列
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
    if (result != SL_RESULT_SUCCESS) {
        LOGD("获取播放队列 GetInterface SL_IID_BUFFERQUEUE failed!");
        return;
    }

    // 4.2 设置回调 void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
    (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue,  // 传入刚刚设置好的队列
                                             bqPlayerCallback,  // 回调函数
                                             this); // 给回调函数的参数
    LOGI("4、设置播放回调函数 Success");

    /**
     * TODO 5、设置播放器状态为播放状态
     */
    (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
    LOGI("5、设置播放器状态为播放状态 Success");


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

以上为音视频播放的主体流程,还有很多细节没有探讨,如音频和视频都开了多个线程来处理,声音和画面肯定是不同步的,需要处理。另外内存开销方面,原始帧frame数据非常大,也需要进行优化。将在下一节分享。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值