Android FFmpeg系列——1 播放视频

FFMPEG+ANativeWinodow渲染播放视频 其实这个demo的实现真的很简单,只需要熟悉FFMPEG的一个基本流程便可轻松实现。后续我会将这个demo的源码上传,大家有需要的可以进行下载,或者私信我直接给你们发。 阅读详情

Android FFmpeg系列——0 编译.so库
Android FFmpeg系列——1 播放视频
Android FFmpeg系列——2 播放音频
Android FFmpeg系列——3 C多线程使用
Android FFmpeg系列——4 子线程播放音视频
Android FFmpeg系列——5 音视频同步播放
Android FFmpeg系列——6 Java 获取播放进度
Android FFmpeg系列——7 实现快进/快退功能

由于公司项目原因,现在才得空来学习关于FFmpeg库的使用。

前言

在使用FFmpeg库的过程中,哎呦,各种心酸!!项目重新创建了N次,调试了N次,终于把视频流播放出来,心里甚是激动呀!

环境搭建

Android Studio 创建Demo项目,记得把 “Include c++ support” 勾上。

这里主要说2点:

  • 项目目录结构;
  • 文件配置,主要是app模块build.gradle 和 CMakeLists.txt 的配置;

项目目录结构

每个人放so位置不一样,这个会影响到配置文件,所以还是放出来会好一点。

项目目录结构

app模块build.gradle

...
android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions"
            }
            ndk {
                abiFilters "armeabi"
            }
        }
    }
    ...
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}
...

CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

# 自己库文件
add_library(
    player
    SHARED
    src/main/cpp/player.cpp
)

# FFmpeg include 文件
include_directories(src/main/cpp/include)

# 编解码库
add_library(
    avcodec-lib
    SHARED
    IMPORTED
)
set_target_properties(
    avcodec-lib
    PROPERTIES IMPORTED_LOCATION
    ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavcodec.so
)

# 滤镜库 暂时没用上
add_library(
    avfilter-lib
    SHARED
    IMPORTED
)
set_target_properties(
    avfilter-lib
    PROPERTIES IMPORTED_LOCATION
    ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavfilter.so
)

# 文件格式库 大部分操作都需要这个库
add_library(
    avformat-lib
    SHARED
    IMPORTED
)
set_target_properties(
    avformat-lib
    PROPERTIES IMPORTED_LOCATION
    ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavformat.so
)

# 工具库
add_library(
    avutil-lib
    SHARED
    IMPORTED
)
set_target_properties(
    avutil-lib
    PROPERTIES IMPORTED_LOCATION
    ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavutil.so
)

# 重采样库 主要用于音频的转换
add_library(
    swresample-lib
    SHARED
    IMPORTED
)
set_target_properties(
    swresample-lib
    PROPERTIES IMPORTED_LOCATION
    ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libswresample.so
)

# 视频格式转换库 主要用于视频的转换
add_library(
    swscale-lib
    SHARED
    IMPORTED
)
set_target_properties(
    swscale-lib
    PROPERTIES IMPORTED_LOCATION
    ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libswscale.so
)

# 主要 android 库 native window 需要这个库
target_link_libraries(
    player
    log
    android
    avcodec-lib
    avfilter-lib
    avformat-lib
    avutil-lib
    swresample-lib
    swscale-lib
)

C 代码

#include <jni.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <android/log.h>

extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}

// Android 打印 Log
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, "player", FORMAT, ##__VA_ARGS__);

/**
 * 播放视频流
 * R# 代表申请内存 需要释放或关闭
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_johan_player_Player_playVideo(JNIEnv *env, jobject instance, jstring path_, jobject surface) {
    // 记录结果
    int result;
    // R1 Java String -> C String
    const char *path = env->GetStringUTFChars(path_, 0);
    // 注册 FFmpeg 组件
    av_register_all();
    // R2 初始化 AVFormatContext 上下文
    AVFormatContext *format_context = avformat_alloc_context();
    // 打开视频文件
    result = avformat_open_input(&format_context, path, NULL, NULL);
    if (result < 0) {
        LOGE("Player Error : Can not open video file");
        return;
    }
    // 查找视频文件的流信息
    result = avformat_find_stream_info(format_context, NULL);
    if (result < 0) {
        LOGE("Player Error : Can not find video file stream info");
        return;
    }
    // 查找视频编码器
    int video_stream_index = -1;
    for (int i = 0; i < format_context->nb_streams; i++) {
        // 匹配视频流
        if (format_context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
        }
    }
    // 没找到视频流
    if (video_stream_index == -1) {
        LOGE("Player Error : Can not find video stream");
        return;
    }
    // 初始化视频编码器上下文
    AVCodecContext *video_codec_context = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(video_codec_context, format_context->streams[video_stream_index]->codecpar);
    // 初始化视频编码器
    AVCodec *video_codec = avcodec_find_decoder(video_codec_context->codec_id);
    if (video_codec == NULL) {
        LOGE("Player Error : Can not find video codec");
        return;
    }
    // R3 打开视频解码器
    result  = avcodec_open2(video_codec_context, video_codec, NULL);
    if (result < 0) {
        LOGE("Player Error : Can not find video stream");
        return;
    }
    // 获取视频的宽高
    int videoWidth = video_codec_context->width;
    int videoHeight = video_codec_context->height;
    // R4 初始化 Native Window 用于播放视频
    ANativeWindow *native_window = ANativeWindow_fromSurface(env, surface);
    if (native_window == NULL) {
        LOGE("Player Error : Can not create native window");
        return;
    }
    // 通过设置宽高限制缓冲区中的像素数量,而非屏幕的物理显示尺寸。
    // 如果缓冲区与物理屏幕的显示尺寸不相符,则实际显示可能会是拉伸,或者被压缩的图像
    result = ANativeWindow_setBuffersGeometry(native_window, videoWidth, videoHeight,WINDOW_FORMAT_RGBA_8888);
    if (result < 0){
        LOGE("Player Error : Can not set native window buffer");
        ANativeWindow_release(native_window);
        return;
    }
    // 定义绘图缓冲区
    ANativeWindow_Buffer window_buffer;
    // 声明数据容器 有3个
    // R5 解码前数据容器 Packet 编码数据
    AVPacket *packet = av_packet_alloc();
    // R6 解码后数据容器 Frame 像素数据 不能直接播放像素数据 还要转换
    AVFrame *frame = av_frame_alloc();
    // R7 转换后数据容器 这里面的数据可以用于播放
    AVFrame *rgba_frame = av_frame_alloc();
    // 数据格式转换准备
    // 输出 Buffer
    int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, videoWidth, videoHeight, 1);
    // R8 申请 Buffer 内存
    uint8_t *out_buffer = (uint8_t *) av_malloc(buffer_size * sizeof(uint8_t));
    av_image_fill_arrays(rgba_frame->data, rgba_frame->linesize, out_buffer, AV_PIX_FMT_RGBA, videoWidth, videoHeight, 1);
    // R9 数据格式转换上下文
    struct SwsContext *data_convert_context = sws_getContext(
            videoWidth, videoHeight, video_codec_context->pix_fmt,
            videoWidth, videoHeight, AV_PIX_FMT_RGBA,
            SWS_BICUBIC, NULL, NULL, NULL);
    // 开始读取帧
    while (av_read_frame(format_context, packet) >= 0) {
        // 匹配视频流
        if (packet->stream_index == video_stream_index) {
            // 解码
            result = avcodec_send_packet(video_codec_context, packet);
            if (result < 0 && result != AVERROR(EAGAIN) && result != AVERROR_EOF) {
                LOGE("Player Error : codec step 1 fail");
                return;
            }
            result = avcodec_receive_frame(video_codec_context, frame);
            if (result < 0 && result != AVERROR_EOF) {
                LOGE("Player Error : codec step 2 fail");
                return;
            }
            // 数据格式转换
            result = sws_scale(
                    data_convert_context,
                    (const uint8_t* const*) frame->data, frame->linesize,
                    0, videoHeight,
                    rgba_frame->data, rgba_frame->linesize);
            if (result <= 0) {
                LOGE("Player Error : data convert fail");
                return;
            }
            // 播放
            result = ANativeWindow_lock(native_window, &window_buffer, NULL);
            if (result < 0) {
                LOGE("Player Error : Can not lock native window");
            } else {
                // 将图像绘制到界面上
                // 注意 : 这里 rgba_frame 一行的像素和 window_buffer 一行的像素长度可能不一致
                // 需要转换好 否则可能花屏
                uint8_t *bits = (uint8_t *) window_buffer.bits;
                for (int h = 0; h < videoHeight; h++) {
                    memcpy(bits + h * window_buffer.stride * 4,
                           out_buffer + h * rgba_frame->linesize[0],
                           rgba_frame->linesize[0]);
                }
                ANativeWindow_unlockAndPost(native_window);
            }
        }
        // 释放 packet 引用
        av_packet_unref(packet);
    }
    // 释放 R9
    sws_freeContext(data_convert_context);
    // 释放 R8
    av_free(out_buffer);
    // 释放 R7
    av_frame_free(&rgba_frame);
    // 释放 R6
    av_frame_free(&frame);
    // 释放 R5
    av_packet_free(&packet);
    // 释放 R4
    ANativeWindow_release(native_window);
    // 关闭 R3
    avcodec_close(video_codec_context);
    // 释放 R2
    avformat_close_input(&format_context);
    // 释放 R1
    env->ReleaseStringUTFChars(path_, path);
}

使用 FFmpeg 播放视频流程如下:

  1. 注册组件
  2. 打开视频文件
  3. 查找视频文件的流信息
  4. 查找视频编码器并打开
  5. 播放视频准备
  6. 视频格式转换准备
  7. 循环读取帧
  8. 解码
  9. 视频格式转换
  10. 播放视频
  11. 释放

代码已经注释得比较清楚了,相信大家看得懂!!

Java 代码

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Play"
        android:onClick="play"
        />

</LinearLayout>

Player Native 代码:

public class Player {

    static {
        System.loadLibrary("player");
    }

    public native void playVideo(String path, Surface surface);

}

Activity 播放代码:


public class MainActivity extends AppCompatActivity {

    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        surfaceView = (SurfaceView) findViewById(R.id.surface_view);
        surfaceHolder = surfaceView.getHolder();
    }

    public void play(View view) {
        String videoPath = Environment.getExternalStorageDirectory() + "/mv.mp4";
        Player player = new Player();
        player.playVideo(videoPath, surfaceHolder.getSurface());
    }

}

效果图

效果图

小结

使用FFmpeg主要是集成时需要谨慎,还有就是对这个库不太了解!

接下来继续摸索!加油!!

基于FFmpeg的简单Android视频播放器 1. 模块分割首先对这个视频播放器所采用的一些部件要清楚。这个播放器主要可以拆分为4个部分:1.解码:FFmpeg2.音频输出:OpenSLES3.视频渲染:OpenGLES这些框架都是基于C的api,因此这次我们的主要工作将会集中在NDK部分。而关于NDK的一些知识,之前的博客也有讲过,所以这个工程会是对之前知识的一次综合运用。为了提高可移植性,对关键部件使用接口来规范其API接口。对于播放器内部,播放的音频数据格式为16位PCM,44.1kHz采样率,双声道。 阅读详情

相关推荐

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

是一种免版税,跨平台,硬件加速的) 音频用于2D和3D音频。它提供对和播放等功能的访问。它是为移动和游戏行业的开发人员设计的,致力于使跨多个平台的应用程序轻松移植。

2401_84009192的博客 1140

【android】ffmpeg实现播放视频

实现ffmpeg的视频解码转为yuv和rgba,并用rgba进行输出。

m0_63553765的博客 946

Android-音视频学习系列-(十)基于-FFmpeg-+-OpenSLES-实现音频万能播放器(1),《Android面试题及解析》分享给大家

现在随着短视频,抖音,快手的流行NDK模块开发也显得越发重要,需要这块人才的企业也越来越多,随之学习这块的人也变多了,音视频的开发,往往是比较难的,而这个比较难的技术就是NDK里面的技术。音视频/高清大图片/人工智能/直播/抖音等等这年与用户最紧密,与我们生活最相关的技术一直都在寻找最终的技术落地平台,以前是windows系统,而现在则是移动系统了,移动系统中又是以Android占比绝大部分为前提,所以AndroidNDK技术已经是我们必备技能了。

2401_84149213的博客 1312

基于FFmpeg的Android播放器

Android视频播放器,解码基于FFmpeg,支持软硬解。视频渲染基于OpenGL ES。音频输出基于oboe。

zuguorui的专栏 2805

Qt + FFmpeg 视频播放器

一、 环境搭建 1. 下载 QT:window 5.7.0版本 FFmpeg: ffmpeg-20200522-38490cb-win32-dev 注意:这里下载 32位dev版本,要和编译器对应(我的mingw是32位的) 2. 加载库 在FFmpeg的解压目录里 将 include/ 的头文件拷贝到自己的项目的工程路径下 从 lib/ 拷贝所需要的库到自己的项目里 #这里是我所用到的库和 pro里的配置 //avcodec-58.dll //avdevice-58.dll //avfilte

qq_40714324的博客 4575

基于ffmpeg+opengl+opensl es的android视频播放器

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

zsy2012919331的博客 9691

Android 本地视频播放器开发 —— ffmpeg解码视频文件中的音频

在上一章中Android本地视频播放器开发--NDK编译FFmpeg能够获取编译出来的ffmpeg库,接下来就是调用ffmpeg来实现解码,这里我们先解码音频,然后在播放音频,同时为了适应性我会用不同的方法进行播放例如使用Android提供的AudioTrack,SDL、OpengAL,OpenSL ES,最终合入视频播放器的是OpenSL ES,这样可以减少CPU的利用率。接下来在这一章中,

mengzhengjie的专栏 1174

FFmpeg安卓流媒体播放器开发实战视频课程 -基于NDK、C++和 FFmpeg Android

课程目标 https://edu.youkuaiyun.com/combo/detail/986---Android 音视频开发(杨万里付费版) 学会编译安卓平台的ffmpeg库(支持硬解码和neon),学会ffmpeg的解码解封装接口,学会opensles播放音频,学会opengles、egl、shader、glsl播放yuv视频,学会音视频同步策略,学会将设计模式应用到实际项目中。 适用人群 ...

疾风剑豪 1368

android视频聚合,Android使用FFmpeg(三)--ffmpeg实现视频播放

前言如果你已经准备好ffmpeg的开发环境,那么我们在这篇文章中实现对视频的一个播放,如果还没有准备好,请看前面的内容。正文 Ok,上图就是使用ffmpeg实现了一个视频的播放的大概流程图,那么,我们将根据流程图来编写代码,这样子,代码的编写就会显得比较简单,比较好理解了。1.注册各大组件,这一步很重要,如果不注册就无法使用后面的函数了。av_register_all();2.在解码之前我们得获取...

weixin_42116713的博客 929

Android 音视频系列之 FFmpeg+OpenGL ES播放视频

目录 一、Android音视频硬解码篇: 1,音视频基础知识 2,音视频硬解码流程:封装基础解码框架 3,音视频播放:音视频同步 4,音视频解封和封装:生成一个MP4 二、使用OpenGL渲染视频画面篇 1,初步了解OpenGL ES 2,使用OpenGL渲染视频画面 3,OpenGL渲染多视频,实现画中画 4,深入了解OpenGL之EGL 5,OpenGL FBO数据缓冲区 6,Android音视频硬编码:生成一个MP4 三、Android FFmpeg音视频解码篇 1,FFmpe

1 742

Android 使用 FFmpeg (二)——视屏流播放简单实现

在Android上使用FFmpeg+ANativeWindow 简单实现视频流播放

一点一刻 7688

Android+FFmpeg+ANativeWindow视频解码播放

使用FFmpeg解码视频文件,利用ANativeWindow渲染视频帧的一个建议播放器介绍。

Glouds的专栏 1万+

NO.9 播放音频

零蚀 前言 本来是准备写些视频压缩,主要功能实现是用ffmpeg.c来实现指令压缩,(其实我想直接导入ffmpeg(120多k)的执行文件,然后在android上用shell命令不更方便?),根据网上的文档,目前适用的都在3.x及以下,4.x一直没成功,可能压缩的方式变了(可能是我太菜了),所以这个内容延后,有时间再找找相关内容。 step 1:播放本地音乐功能 step 2:视频音频...

qq_38315348的博客 260

实现android ffmpeg播放视频

前面两篇文章已经可以生成so,并且可以在Android studio中通过配置可以运行,并且可以打印出ffmpeg的配置信息。 准备: 在进行播放视频时需要一个吧yuv图像数据转换成argb的图像数据,这里还需要一个libyuv.so 的一个so库。可以通过这篇文章生成这个so :https://blog.youkuaiyun.com/quan648997767/article/details/7088028...

厚德出品,必是精品 4338

FFmpeg在Android上的视频播放解决方案示例

集成FFmpeg到Android项目涉及以下几个步骤:下载和配置NDK: FFmpeg使用C/C++编写,因此需要配置Android NDK环境。编译FFmpeg库: 根据Android的架构进行FFmpeg的编译,生成so文件。集成so文件和头文件: 将编译生成的库文件和头文件引入到Android项目中。下面,我们将详细说明每个步骤。Java Native Interface(JNI)是Java提供的一种标准编程接口,允许Java代码与其他语言写的代码进行交互。

weixin_36427956的博客 1197

AURIX-TC3xx-GTM详解六-DPLL(Digital PLL Module)-寄存器1-DPLL寄存器

主要有下图这些寄存器2、

m0_69929716的博客 442

【Android FFMPEG 开发】FFMPEG 视频播放进度控制 ( 显示播放进度 | 拖动进度条播放 )

I . FFMPEG 播放进度控制 II . FFMPEG 播放视频 ( 效果展示 ) III . FFMPEG 获取视频时长 IV . FFMPEG 视频播放进度获取 V . FFMPEG 设置播放进度

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 5198

【Dumper-7】入门指南 - 带你领略不一样的UE世界

摘要: Dumper-7是一款用于分析UE4/UE5游戏的逆向工具,可自动生成游戏内部结构的C++ SDK。支持Visual Studio、CMake和Xmake三种构建方式,通过DLL注入目标游戏后,自动识别引擎结构并输出C++ SDK文件(默认保存在C:\Dumper-7目录)。该工具适用于游戏Mod开发、安全研究和机制分析,提供完整的类层次映射、属性/函数识别功能。使用时需注意x64平台配置,生成SDK后可集成到自定义DLL项目中,通过示例代码展示如何查找游戏对象、访问世界状态和调用引擎函数。

kinghzking的专栏 437
上一篇: Android FFmpeg 编译.so库
下一篇: Android FFmpeg系列——2 播放音频
JohanMan
博客等级 码龄10年 72粉丝 131原创
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值