热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一个使用FFmpeg库读取3gp视频的例子-Android中使用FFmpeg媒体库

在续系列文章在32位的Ubuntu11.04中为AndroidNDKr6编译FFmpeg0.8.1版-Android中使用FFmpeg媒体库(一)和在Android中通过jni方式使用编译好的F

在续系列文章在32位的Ubuntu 11.04中为Android NDK r6编译FFmpeg0.8.1版-Android中使用FFmpeg媒体库(一)和在Android中通过jni方式使用编译好的FFmpeg库-Android中使用FFmpeg媒体库(二)文章后,本文将根据github中churnlabs的一个开源项目,来深入展开说明如何使用FFmpeg库进行多媒体的开发。
本文中的代码来自于https://github.com/churnlabs/android-ffmpeg-sample,更多的可以参考这个项目代码。我会在代码中加一些自己的注释。感谢作者churnlabs给我们提供这么好的例子以供我们学习。
在Android的一些系统层应用开发大多数是采用jni的方式调用,另外对于一些比较吃CPU或者处理逻辑比较复杂的程序,也可以考虑使用jni方式来封装。可以提高程序的执行效率。

本文涉及到以下几个方面:
1 将3gp文件push到模拟机器的sdcard中
2 写jni代码,内部调用ffmpeg库的方法,编译jni库
3 loadLibrary生成的库,然后撰写相应的java代码
4 执行程序,并查看最终运行结果。
最终程序的显示效果如下:

1 使用eclipse的DDMS工具,将vid.3pg push到sdcard中


2 撰写相应的jni文件

/*
* Copyright 2011 - Churn Labs, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/*
* This is mostly based off of the FFMPEG tutorial:
* http://dranger.com/ffmpeg/
* With a few updates to support Android output mechanisms and to update
* places where the APIs have shifted.
*/

#include
#include
#include
#include
#include

//包含ffmpeg库头文件,这些文件都直接方案jni目录下
#include
#include
#include

#define LOG_TAG "FFMPEGSample"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

/* Cheat to keep things simple and just use some globals. */
//全局对象
AVFormatContext *pFormatCtx;
AVCodecContext *pCodecCtx;
AVFrame *pFrame;
AVFrame *pFrameRGB;
int videoStream;

/*
* Write a frame worth of video (in pFrame) into the Android bitmap
* described by info using the raw pixel buffer. It's a very inefficient
* draw routine, but it's easy to read. Relies on the format of the
* bitmap being 8bits per color component plus an 8bit alpha channel.
*/

//定义的静态方法,将某帧AVFrame在Android的Bitmap中绘制
static void fill_bitmap(AndroidBitmapInfo* info, void *pixels, AVFrame *pFrame)
{
uint8_t *frameLine;

int yy;
for (yy = 0; yy height; yy++) {
uint8_t* line = (uint8_t*)pixels;
frameLine = (uint8_t *)pFrame->data[0] + (yy * pFrame->linesize[0]);

int xx;
for (xx = 0; xx width; xx++) {
int out_offset = xx * 4;
int in_offset = xx * 3;

line[out_offset] = frameLine[in_offset];
line[out_offset+1] = frameLine[in_offset+1];
line[out_offset+2] = frameLine[in_offset+2];
line[out_offset+3] = 0;
}
pixels = (char*)pixels + info->stride;
}
}

//定义java回调函数,相当与 com.churnlabs中的ffmpegsample中的MainActivity类中的openFile方法。
void Java_com_churnlabs_ffmpegsample_MainActivity_openFile(JNIEnv * env, jobject this)
{
int ret;
int err;
int i;
AVCodec *pCodec;
uint8_t *buffer;
int numBytes;
//注册所有的函数
av_register_all();
LOGE("Registered formats");
//打开sdcard中的vid.3gp文件
err = av_open_input_file(&pFormatCtx, "file:/sdcard/vid.3gp", NULL, 0, NULL);
LOGE("Called open file");
if(err!=0) {
LOGE("Couldn't open file");
return;
}
LOGE("Opened file");

if(av_find_stream_info(pFormatCtx)<0) {
LOGE("Unable to get stream info");
return;
}

videoStream = -1;
//定义设置videoStream
for (i=0; inb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
videoStream = i;
break;
}
}
if(videoStream==-1) {
LOGE("Unable to find video stream");
return;
}

LOGI("Video stream is [%d]", videoStream);
//定义编码类型
pCodecCtx=pFormatCtx->streams[videoStream]->codec;
//获取解码器
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL) {
LOGE("Unsupported codec");
return;
}
//使用特定的解码器打开
if(avcodec_open(pCodecCtx, pCodec)<0) {
LOGE("Unable to open codec");
return;
}
//分配帧空间
pFrame=avcodec_alloc_frame();
//分配RGB帧空间
pFrameRGB=avcodec_alloc_frame();
LOGI("Video size is [%d x %d]", pCodecCtx->width, pCodecCtx->height);
//获取大小
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
分配空间
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
}
//定义java回调函数,相当与 com.churnlabs中的ffmpegsample中的MainActivity类中的drawFrame方法。
void Java_com_churnlabs_ffmpegsample_MainActivity_drawFrame(JNIEnv * env, jobject this, jstring bitmap)
{
AndroidBitmapInfo info;
void* pixels;
int ret;

int err;
int i;
int frameFinished = 0;
AVPacket packet;
static struct SwsContext *img_convert_ctx;
int64_t seek_target;

if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) <0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return;
}
LOGE("Checked on the bitmap");

if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) <0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
LOGE("Grabbed the pixels");

i = 0;
while((i==0) && (av_read_frame(pFormatCtx, &packet)>=0)) {
if(packet.stream_index==videoStream) {
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

if(frameFinished) {
LOGE("packet pts %llu", packet.pts);
// This is much different than the tutorial, sws_scale
// replaces img_convert, but it's not a complete drop in.
// This version keeps the image the same size but swaps to
// RGB24 format, which works perfect for PPM output.
int target_width = 320;
int target_height = 240;
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt,
target_width, target_height, PIX_FMT_RGB24, SWS_BICUBIC,
NULL, NULL, NULL);
if(img_convert_ctx == NULL) {
LOGE("could not initialize conversion context\n");
return;
}
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

// save_frame(pFrameRGB, target_width, target_height, i);
fill_bitmap(&info, pixels, pFrameRGB);
i = 1;
}
}
av_free_packet(&packet);
}

AndroidBitmap_unlockPixels(env, bitmap);
}

//内部调用函数,不对外,用来查找帧
int seek_frame(int tsms)
{
int64_t frame;

frame = av_rescale(tsms,pFormatCtx->streams[videoStream]->time_base.den,pFormatCtx->streams[videoStream]->time_base.num);
frame/=1000;

if(avformat_seek_file(pFormatCtx,videoStream,0,frame,frame,AVSEEK_FLAG_FRAME)<0) {
return 0;
}

avcodec_flush_buffers(pCodecCtx);

return 1;
}
//定义java回调函数,相当与 com.churnlabs中的ffmpegsample中的MainActivity类中的drawFrameAt方法。
void Java_com_churnlabs_ffmpegsample_MainActivity_drawFrameAt(JNIEnv * env, jobject this, jstring bitmap, jint secs)
{
AndroidBitmapInfo info;
void* pixels;
int ret;

int err;
int i;
int frameFinished = 0;
AVPacket packet;
static struct SwsContext *img_convert_ctx;
int64_t seek_target;

if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) <0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return;
}
LOGE("Checked on the bitmap");

if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) <0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
LOGE("Grabbed the pixels");

seek_frame(secs * 1000);

i = 0;
while ((i== 0) && (av_read_frame(pFormatCtx, &packet)>=0)) {
if(packet.stream_index==videoStream) {
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

if(frameFinished) {
// This is much different than the tutorial, sws_scale
// replaces img_convert, but it's not a complete drop in.
// This version keeps the image the same size but swaps to
// RGB24 format, which works perfect for PPM output.
int target_width = 320;
int target_height = 240;
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt,
target_width, target_height, PIX_FMT_RGB24, SWS_BICUBIC,
NULL, NULL, NULL);
if(img_convert_ctx == NULL) {
LOGE("could not initialize conversion context\n");
return;
}
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

// save_frame(pFrameRGB, target_width, target_height, i);
fill_bitmap(&info, pixels, pFrameRGB);
i = 1;
}
}
av_free_packet(&packet);
}

AndroidBitmap_unlockPixels(env, bitmap);
}

3 撰写相应的Android.mk文件

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := ffmpegutils
LOCAL_SRC_FILES := native.c

LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_LDLIBS := -L$(NDK_PLATFORMS_ROOT)/$(TARGET_PLATFORM)/arch-arm/usr/lib -L$(LOCAL_PATH) -lavformat -lavcodec -lavdevice -lavfilter -lavcore -lavutil -lswscale -llog -ljnigraphics -lz -ldl -lgcc

include $(BUILD_SHARED_LIBRARY)

这里需要注意一下文件的目录情况,我截图说明一下。

在Android.mk中有意个LOCAL_C_INCLUDES :=$(LOCAL_PATH)/include指明了相应的FFmpeg的头文件路径。故在代码中包含

#include 
#include
#include

就可以。

4 调用ndk-build,生成libffmpegutils.so文件,将这个文件拷贝到/root/develop/android-ndk-r6/platforms/android-8/arch-arm/usr/lib目录,使得我们在下面使用Android AVD2.2的时候,可以加载到这个so文件。

5 撰写相应的Eclipse项目代码,由于在native.c文件中指明了项目的工程名词以及类名词还有函数名词,故我们的项目为com.churnlabs.ffmpegsample下面的MainActivity.java文件

package com.churnlabs.ffmpegsample;

import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends Activity {
private static native void openFile();
private static native void drawFrame(Bitmap bitmap);
private static native void drawFrameAt(Bitmap bitmap, int secs);

private Bitmap mBitmap;
private int mSecs = 0;

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

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(new VideoView(this));
setContentView(R.layout.main);

mBitmap = Bitmap.createBitmap(320, 240, Bitmap.Config.ARGB_8888);
openFile();

Button btn = (Button)findViewById(R.id.frame_adv);
btn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
drawFrame(mBitmap);
ImageView i = (ImageView)findViewById(R.id.frame);
i.setImageBitmap(mBitmap);
}
});

Button btn_fwd = (Button)findViewById(R.id.frame_fwd);
btn_fwd.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mSecs += 5;
drawFrameAt(mBitmap, mSecs);
ImageView i = (ImageView)findViewById(R.id.frame);
i.setImageBitmap(mBitmap);
}
});

Button btn_back = (Button)findViewById(R.id.frame_back);
btn_back.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mSecs -= 5;
drawFrameAt(mBitmap, mSecs);
ImageView i = (ImageView)findViewById(R.id.frame);
i.setImageBitmap(mBitmap);
}
});
}
}

6 编译运行即可,最终效果图



7 项目代码下载:

https://github.com/churnlabs/android-ffmpeg-sample/zipball/master

参考:
1 https://github.com/churnlabs/android-ffmpeg-sample
2 http://www.360doc.com/content/10/1216/17/474846_78726683.shtml
3 https://github.com/prajnashi

 

本文同发布地址:

http://doandroid.info/?p=497

感谢原作者分享,转载:http://www.cnblogs.com/doandroid/archive/2011/11/09/2242558.html


推荐阅读
  • CentOS7.8下编译muduo库找不到Boost库报错的解决方法
    本文介绍了在CentOS7.8下编译muduo库时出现找不到Boost库报错的问题,并提供了解决方法。文章详细介绍了从Github上下载muduo和muduo-tutorial源代码的步骤,并指导如何编译muduo库。最后,作者提供了陈硕老师的Github链接和muduo库的简介。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • Android日历提醒软件开源项目分享及使用教程
    本文介绍了一款名为Android日历提醒软件的开源项目,作者分享了该项目的代码和使用教程,并提供了GitHub项目地址。文章详细介绍了该软件的主界面风格、日程信息的分类查看功能,以及添加日程提醒和查看详情的界面。同时,作者还提醒了读者在使用过程中可能遇到的Android6.0权限问题,并提供了解决方法。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • 本文介绍了一些Java开发项目管理工具及其配置教程,包括团队协同工具worktil,版本管理工具GitLab,自动化构建工具Jenkins,项目管理工具Maven和Maven私服Nexus,以及Mybatis的安装和代码自动生成工具。提供了相关链接供读者参考。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 本文介绍了Codeforces Round #321 (Div. 2)比赛中的问题Kefa and Dishes,通过状压和spfa算法解决了这个问题。给定一个有向图,求在不超过m步的情况下,能获得的最大权值和。点不能重复走。文章详细介绍了问题的题意、解题思路和代码实现。 ... [详细]
  • 本文介绍了在Ubuntu系统中清理残余配置文件和无用内容的方法,包括清理残余配置文件、清理下载缓存包、清理不再需要的包、清理无用的语言文件和清理无用的翻译内容。通过这些清理操作可以节省硬盘空间,提高系统的运行效率。 ... [详细]
  • loader资源模块加载器webpack资源模块加载webpack内部(内部loader)默认只会处理javascript文件,也就是说它会把打包过程中所有遇到的 ... [详细]
author-avatar
西单女孩超炫
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有