关闭

[置顶] 安卓用ffmeg解码

标签: android
1911人阅读 评论(0) 收藏 举报
分类:

本文章是用ffmeg解码封装格式(如mp4)转换为yuv420p保存到本地,本文是结合雷霄骅博客ppt和某地方学习的一个笔记(说出来等下被认为做广告就尴尬了)

封装格式

这里写图片描述

视频编码数据

将封装格式解压后可以得到压缩过的音视频等.
将压缩过的视频解压后可以得到 视频像素数据(RGB,YUV等).常见的视频压缩格式有H.264, MPEG4等…
这里写图片描述

YUV420P格式介绍

YUV是视频像素格式,在压缩视频格式解压可以得到,YUV也有很多种格式.下面举例三种.
1. Y代表亮度
2. uv代表色度

因为人对亮度的敏感远大于色度

转载地址
用三个图来直观地表示采集的方式吧,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量
这里写图片描述

先记住下面这段话,以后提取每个像素的YUV分量会用到。

YUV 4:4:4采样,每一个Y对应一组UV分量。
YUV 4:2:2采样,每两个Y共用一组UV分量。
YUV 4:2:0采样,每四个Y共用一组UV分量。

上面大家看最后一幅图即可.
这里写图片描述

编译ffmpeg的so库

在ffmpeg历史版本中选择一个版本下载到本地
ffmpeg历史发布版本连接

这里写图片描述

因为我们安卓是linux系统,所以我们下载到本地放入到linux系统编译后给安卓使用.

1下载压缩包放入linux并解压

下面是解压后目录
这里写图片描述

我们打开文件夹看看.
这里写图片描述

2编写脚本控制configure生成so

创建一个build_android.sh文件作为脚本

#!/bin/bash
make clean
export NDK=/usr/ndk/android-ndk-r10e
export SYSROOT=$NDK/platforms/android-9/arch-arm/
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64
export CPU=arm
export PREFIX=$(pwd)/android/$CPU
export ADDI_CFLAGS="-marm"

./configure --target-os=linux \
--prefix=$PREFIX --arch=arm \
--disable-doc \
--enable-shared \
--disable-static \
--disable-yasm \
--disable-symver \
--enable-gpl \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install

注意上面的几个地方

#你NDK存放在linux的路径,如果你没有下载那么请自行下载
export NDK=/usr/ndk/android-ndk-r10e
# 输出编译后的so路径 $(pwd)是当前路径
export PREFIX=$(pwd)/android/$CPU
#你要编译版本
export CPU=arm

上传到服务器ffmpeg解压目录下.
这里写图片描述

修改解压后ffmpeg目录文件权限
这里写图片描述

上面的代码会执行修改ffmpeg目录和子目录的权限为可读可写可执行(子目录也要不然有坑)

最后执行我们的脚本(大约会进行10分钟)
这里写图片描述

编译完成后会在 ffmpeg目录下的android的arm下生成两个文件
一个是include文件夹 ,另一个是lib
1. include 包含编译生成的so对应头文件
2. lib 生成的so文件

我们打开lib目录查看:
这里写图片描述

解释:
libXXX.so.YYYY,DDD
上面XXX是so 功能类名
YYYY,DDDD是版本号.这样库在安卓是无法使用的

有人又会说了,目录下面不是有libXXX.so吗?
他只是个linux的软连接,就像快捷方式一样

解决办法:
修改目录下configure文件部分内容如下
注释部分为原来内容

#注释的部分
#SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
#LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
#SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
#SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'

#自己写的部分
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'

修改之后重新 执行./build_android即可


Eclipse编译之旅

将上面的lib中的so文件(当然也可以将lib文件夹复制过去)拷贝到eclipse目录的jni下,将include文件夹也放入jin下
这里写图片描述

修改Android.mk文件

LOCAL_PATH := $(call my-dir)
#ffmpeg lib
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec-56.so
#LOCAL_EXPORT_C_INCLUDES :=  $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := libavdevice-56.so
#LOCAL_EXPORT_C_INCLUDES :=  $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-5.so
#LOCAL_EXPORT_C_INCLUDES :=  $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-56.so
#LOCAL_EXPORT_C_INCLUDES :=  $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-54.so
#LOCAL_EXPORT_C_INCLUDES :=  $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)
LOCAL_MODULE := postproc
LOCAL_SRC_FILES := libpostproc-53.so
#LOCAL_EXPORT_C_INCLUDES :=  $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-1.so
#LOCAL_EXPORT_C_INCLUDES :=  $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-3.so

#LOCAL_EXPORT_C_INCLUDES :=  $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)

LOCAL_MODULE    := DemoFFmepeg
LOCAL_SRC_FILES := DemoFFmepeg.c
LOCAL_LDLIBS+= -llog
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale
#下面这行是导入include文件中头文件,这样做我们就可以在项目中
#直接include"libscan/xxx.h"而不是include"include/libscan/xxx.h"
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
include $(BUILD_SHARED_LIBRARY)

我们先看看MainActivity.java 文件

package com.fmple.demoffmepeg;

import java.io.File;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;


public class MainActivity extends Activity {

    static{
        System.loadLibrary("DemoFFmepeg");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final File inputFile = new File(Environment.getExternalStorageDirectory(),"a.mov");
        final File outFile = new File(Environment.getExternalStorageDirectory(),"b.yuv");
        //由于是耗时操作 所以开了个线程
        new Thread(){
            public void run() {
                 ffmpeg(inputFile.getAbsolutePath(), outFile.getAbsolutePath());
            };
        }.start();


    }

   /**
    * 
    * @param input 视频文件的输入路径
    * @param out 把视频文件解码成yuv格式输出路径
    */
   public native void ffmpeg(String input,String out);


}

上面也没什么好说的,就是创建activity的时候调用我们的一个jni方法ffmpeg

最后看看方法的实现文件DemoFFmepeg.c
下面我完整翻译了所用到的API耗时挺久的,英语不好…

#include <jni.h>

#include<stdio.h>
#include<android/log.h>
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO," FMY",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"FMY",FORMAT,##__VA_ARGS__);
char * output_cstr;
/**
 * 这里提供了另一种方式转化为saveYUV420 本案例不用 大家可以自己体验下此方法(这个方法在网上看到过)
 */
static void saveYUV420P(unsigned char *buf,int wrap,int xsize, int ysize){
    FILE *fileOut = NULL;

    int i;
    if(buf ==NULL){
        LOGE("缓存为空");
        return ;
    }

    fileOut = fopen(output_cstr,"ab+");

    for (i = 0;i<ysize; ++i) {

        fwrite(buf+i*wrap,1,xsize,fileOut);

    }
    fflush(fileOut);
    fclose(fileOut);

}
JNIEXPORT void JNICALL Java_com_fmple_demoffmepeg_MainActivity_ffmpeg
(JNIEnv * env, jobject jobj, jstring input, jstring out){


    char * input_char = (*env)->GetStringUTFChars(env,input,NULL);

    output_cstr = (*env)->GetStringUTFChars(env,out,NULL);


    //头文件libavformat/avformat.h
    //注册所有组件
    /**
     * 初始化libavformat和注册所有的 muxers, demuxers 和协议,如果你不想使用次函数,
     * 则可以手动选择你想要的支持格式
     * 详情你可以选择下面的函数查询
     * @see av_register_input_format()
     * @see av_register_output_format()
     *
     * muxer是合并将视频文件、音频文件和字幕文件合并为某一个视频格式。如,可将a.avi, a.mp3, a.srt用muxer合并为mkv格式的视频文件。
     * demuxer是拆分这些文件的。
     */
    av_register_all();

    // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。
    AVFormatContext * pFormatCtx = avformat_alloc_context();

    /**
     * 打开输入流并且读取头信息。但解码器没有打开
     * 这个输入流必须使用avformat_close_input()关闭
     * @param ps(第一个参数的形参名称) 指向 你由你提供AVFormatContext(AVFormatContext是由avformat_alloc_context函数分配的)。
     * 有可能ps指向空,在这种情况下,AVFormatContext由此函数分配并写入ps。
     * 注意: 你提供的AVFormatContext在函数执行失败的时候将会被释放
     * @param url 你要打开视频文件路径.
     * @param fmt  如果不为空,那么这个参数将强制作为输入格式,否则自动检索
     * @param options 一个关于AVFormatContext and demuxer-private 选项的字典.
     * 返回时,此参数将被销毁,并替换为包含未找到的选项的dict。有可能是空的
     *
     * @return 返回0表示成功, 一个负数常量AVERROR是失败的.
     *
     * @note 如果你想自定义IO,你需要预分配格式内容并且设置pd属性
     */
    if(avformat_open_input(&pFormatCtx,input_char,NULL,NULL)!=0){
        LOGE("NDK>>>%s","avformat_open_input打开失败");
        return;
    }

    //上面打开输入流后会将视频封装格式信息写入AVFormatContext中那么我们下一步就可以得到一些展示信息
    /**
     *
     * 读取媒体文件中的数据包以获取流信息,这个对于对于文件格式没有头信息的很有帮助,比如说mpeg
     * 这个函数还可以计算在MPEG-2重复帧模式的真实帧速率。
     * 逻辑文件位置不会被这个函数改变
     * 检索过的数据包或许会缓存以供后续处理
     * @param ic  第一个参数 封装格式上下文
     * @param options
     *              如果不为空, 一个长度为 ic.nb_streams (封装格式所有流,字幕 视频 音频等) 的字典。
     *              字典中第i个成员  包含一个对应ic第i个流中对的编码器。
     *              在返回时,每个字典将会填充没有找到的选项
     * @return 如果返回>=0 代表成功, AVERROR_xxx 表示失败
     *
     * @note 这个函数 不保证能打开所有编码器,所以返回一个非空的选项是一个完全正常的行为
     *
     *
     * @todo
     *  下个版本目标无视即可
     * Let the user decide somehow what information is needed so that
     *       we do not waste time getting stuff the user does not need.
     */
    if( avformat_find_stream_info(pFormatCtx,NULL)<0){
        LOGE("NDK>>>%s","avformat_find_stream_info失败");
        return ;
    }
    LOGE("NDK>>>%s","成功");
    //  //输出视频信息
    //  LOGI("视频的文件格式:%s",pFormatCtx->iformat->name);
    //  LOGI("视频时长:%d", (pFormatCtx->duration)/1000000);

    //获取视频流的索引位置
    //遍历所有类型的流(音频流、视频流、字幕流),找到视频流
    int v_stream_idx = -1;
    int i = 0;
    //遍历封装格式中所有流
    for (; i < pFormatCtx->nb_streams; ++i) {

        //获取视频流pFormatCtx->streams[i]
        //pFormatCtx->streams[i]->codec获取编码器
        //codec_type获取编码器类型
        //当前流等于视频 记录下标
        if (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_VIDEO) {
            v_stream_idx = i;
            break;
        }
    }
    if (v_stream_idx==-1) {
        LOGE("没有找视频流")
    }else{
        LOGE("找到视频流")
    }

    //编码器上下文结构体,保存了视频(音频)编解码相关信息
    //得到视频流编码器
    AVCodecContext *pCodecCtx = pFormatCtx->streams[v_stream_idx]->codec;

    //   每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
    AVCodec *pCodec =avcodec_find_decoder(pCodecCtx->codec_id);

    //(迅雷看看,找不到解码器,临时下载一个解码器)
    if (pCodec == NULL)
    {
        LOGE("%s","找不到解码器\n");
        return;
    }else{
        LOGE("%s","找到解码器\n");
    }


    //打开解码器
    /**
     * 初始化 指定AVCodecContext去使用 给定的AVCodec
     * 在使用之前函数必须使用avcodec_alloc_context3()分配上下文。
     *
     * 以下函数 avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(),
     * avcodec_find_decoder() and avcodec_find_encoder() 提供了一个简便的得到一个解码器的方法
     *
     * @warning 这个函数线程不是安全的
     *
     * @note 在使用解码程序之前,始终调用此函数 (如 @ref avcodec_decode_video2()).
     * 下面是示例代码
     * @code
     * avcodec_register_all();
     * av_dict_set(&opts, "b", "2.5M", 0);
     * codec = avcodec_find_decoder(AV_CODEC_ID_H264);
     * if (!codec)
     *     exit(1);
     *
     * context = avcodec_alloc_context3(codec);
     *
     * if (avcodec_open2(context, codec, opts) < 0)
     *     exit(1);
     * @endcode
     *
     *
     * @param avctx 要初始化的编码器
     * @param codec 用这个codec去打开给定的上下文编码器.如果 codec 不为空 那么必须
     * 事先用avcodec_alloc_context3和avcodec_get_context_defaults3传递给这个context,那么这个codec
     * 要么为NULL要么就是上面调用函数所使用的codec
     *
     * @param
     *
     * 选项填充AVCodecContext和编解码器私有选项的字典。返回时,此对象将填充未找到的选项。
     *
     * @return 返回0表示成功, 负数失败
     * @see avcodec_alloc_context3(), avcodec_find_decoder(), avcodec_find_encoder(),
     *      av_dict_set(), av_opt_find().
     */
    if(avcodec_open2(pCodecCtx,pCodec,NULL)==0){
        LOGE("%s","打开编码器成功\n");
    }else{
        LOGE("%s","打开编码器失败\n");
        return;
    }
    //输出视频信息
    LOGE("视频的文件格式:%s",pFormatCtx->iformat->name);
    //得到视频播放时长
    if(pFormatCtx->duration != AV_NOPTS_VALUE){
        int hours, mins, secs, us;
        int64_t duration = pFormatCtx->duration + 5000;
        secs = duration / AV_TIME_BASE;
        us = duration % AV_TIME_BASE;
        mins = secs / 60;
        secs %= 60;
        hours = mins/ 60;
        mins %= 60;
        LOGE("%02d:%02d:%02d.%02d\n", hours, mins, secs, (100 * us) / AV_TIME_BASE);

    }
    LOGE("视频的宽高:%d,%d",pCodecCtx->width,pCodecCtx->height);
    LOGE("解码器的名称:%s",pCodec->name);


    //存储一帧压缩编码数据。
    AVPacket *packet =av_malloc(sizeof(AVPacket));

    //输出转码文件地址
        FILE *fp_yuv = fopen(output_cstr,"wb+");

    //AVFrame用于存储解码后的像素数据(YUV)
    //内存分配
    AVFrame *pFrame = av_frame_alloc();

    //YUV420转码用
    AVFrame *pFrameYUV = av_frame_alloc();

    //avpicture_get_size()函数介绍:
    //
    /**
     * 如果给定存储图片的格式,那么计算给定的宽高所占用的大小
     *
     * @param pix_fmt   图片像素格式
     * @param width     图片宽
     * @param height     图片高
     * @return 返回计算的图片缓存大小或者错误情况下的负数错误代码
     *
     *
     * 这里计算缓存区的大小,但是没有分配,这里是用来后面转码使用
     */
    uint8_t *out_buffer = av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height));

    //初始化缓冲区
    /**
     * 基于指定的图片参数和提供的图片缓存区去设置图片字段
     *
     * 使用ptr所指向的图片数据缓存  填充图片属性
     *
     * 如果 ptr是空,这个函数仅填充图片行大小(linesize)的数组并且返回图片缓存请求的大小
     *
     * 要分配图片缓存并且再一次填充图片数据请使用 avpicture_alloc().
     * @param picture       要填充的图片
     * @param ptr           存储图片的数据的缓存区, or NULL
     * @param pix_fmt       图片像素格式
     * @param width         图片宽
     * @param height        图片高
     * @return 返回请求的字节大小,在错误的情况下返回负数
     *
     * @see av_image_fill_arrays()
     */
    avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
    //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
    /**
     *分配和返回 SwsContext. You need it to perform
     * scaling/conversion operations using sws_scale().
     *
     * @param srcW 原始图宽
     * @param srcH 原始图高
     * @param srcFormat 原始图格式
     * @param dstW 目标图宽
     * @param dstH 不解释
     * @param dstFormat 不解释
     * @param flags 指定一个标志用于重新调整算法和选项
     *  具体参考:http://blog.csdn.net/leixiaohua1020/article/details/12029505
     * @return 一个指定分配内容的指针, 错误情况返回空
     * @note this function is to be removed after a saner alternative is
     *       written
     */
        struct SwsContext *sws_ctx =sws_getContext(pCodecCtx->width, pCodecCtx->height,pCodecCtx->pix_fmt,
                pCodecCtx->width, pCodecCtx->height,AV_PIX_FMT_YUV420P,
                SWS_BICUBIC, NULL, NULL, NULL);


    //标志位
    int got_picture, ret;

    //读取每一帧
    /**
     *返回下一帧的流
     * 此函数返回存储在文件中的内容,并且不会验证解码器有什么有效帧。
     * 函数将存储在文件中的帧进行分割 并且返回给每一个调用者。
     *
     * 函数不会删除在有效帧之间的无效数据 以便在可能解码过程中提供解码器最大的信息帮助
     * 如果 pkt->buf 是空的,那么这个对应数据包是有效的直到下一次调用av_read_frame()
     * 或者直到使用avformat_close_input().否则包无期限有效
     * 在这两种情况下 这个数据包当你不在需要的时候,你必须使用使用av_free_packet释放它
     * 对于视屏,数据包刚好只包含一帧.对于音频,如果它每一帧是一个已知固定大小的,那么他包含整数帧(如. PCM or ADPCM data)
     * 如果音频帧具有可变大小(如. MPEG audio),那么他只包含一帧
     * pkt->pts, pkt->dts and pkt->duration 始终在AVStream.time_base 单位设置正确的数值
     *(如果这个格式无法提供.那么进行猜测)
     * 如果视频格式有B帧那么pkt->pts可以是 AV_NOPTS_VALUE.如果你没有解压他的有效部分那么最好依靠pkt->dts
     *
     * @return 0表示成功, < 0 错误或者文结束
     */
    while(av_read_frame(pFormatCtx,packet)>=0){

        //一个包里有很多种类型如音频视频等 所以判断 这个包对应的流的在封装格式的下表
        //如果这个包是视频频包那么得到压缩的视频包
        if (packet->stream_index==v_stream_idx) {
            LOGE("测试");
            /**
             * 解码视频帧 从avpkt->data读取数据并且解码avpkt->size的大小后转化为图片.
             * 一些解码器可以支持在一个ACpacket中存在多帧的情况,像这样的解码器将只解码第一帧
             *
             * @warning  输入缓存区必须 实际读取的字节流小于 FF_INPUT_BUFFER_PADDING_SIZE,
             * 一些优化过的比特流 读取32位或者64字节 的时候可以一次性读取完
             *
             * @warning 在缓冲器的buf结尾设置0以确保被破坏的MPEG流不会发生超线程
             *
             * @note 有 CODEC_CAP_DELAY 才能设置一个在输入和输出之间的延迟,这些需要使用avpkt->data=NULL,
             *  在结束返回剩余帧数的时候avpkt->size=0
             *
             * @note  这个AVCodecContext 在数据包传入解码器之前必须调用avcodec_open2
             *
             *
             * @param avctx 解码器上下文
             *
             * @param[out] 解码的视频帧图片将会被存储在AVFrame.
             *                 使用av_frame_alloc 得到一个AVFrame,
             *                 编码器将会分配 使用  AVCodecContext.get_buffer2() 回调
             *                 的实际图片的内存.
             *                 当AVCodecContext.refcounted_frames 设置为1,这帧(frame)是引用计数,并且返回
             *                 的引用计数是属于调用者的.
             *             frame在长实际不使用的时候调用者必须调用av_frame_unref()就行释放
             *             如果av_frame_is_writable()返回1那么调用者可以安全的写入到这个frame中。
             *                 当AVCodecContext.refcounted_frames设置为0,返回的引用属于解码器,
             *                只有下次使用这个函数或者关闭或者刷新这个编码器之前有效。调用者不会写入它
             *
             *@param[in,out] got_picture_ptr 如果为0表示不能解压, 否者它不是0.
             *
             * @param[in] avpkt 这个输入的avpkt包含输入缓存区
             *              你能使用av_init_packet()创建像这样的packet然后设置数据和大小,
             *              一些解码器还可以添加一些其他字段 比如  flags&AV_PKT_FLAG_KEY  flags&AV_PKT_FLAG_KEY
             *          所有解码器都设计为尽可能少地使用
             *
             * @return 再错误时返回一个负数 , 否则返回使用字节数或者或者0(没有帧被解压返回0)otherwise the number of bytes
             *
             */
            ret=avcodec_decode_video2(pCodecCtx,pFrame,&got_picture,packet);
            if(ret>=0){
                LOGE("解压成功");
                //AVFrame转为像素格式YUV420,宽高
                //2 6输入、输出数据
                //3 7输入、输出画面一行的数据的大小 AVFrame 转换是一行一行转换的
                //4 输入数据第一列要转码的位置 从0开始
                //5 输入画面的高度
                sws_scale(sws_ctx,pFrame->data,pFrame->linesize,0,pCodecCtx->height,pFrameYUV->data,pFrameYUV->linesize);

                //输出到YUV文件
                //AVFrame像素帧写入文件
                //data解码后的图像像素数据(音频采样数据)
                //Y 亮度 UV 色度(压缩了) 人对亮度更加敏感
                //U V 个数是Y的1/4
                int y_size = pCodecCtx->width * pCodecCtx->height;

                fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);
                fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv);
                fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv);

            }
        }
        av_free_packet(packet);
    }


    (*env)->ReleaseStringUTFChars(env,input,input_char);

    (*env)->ReleaseStringUTFChars(env,out,output_cstr);
    //关闭文件
    fclose(fp_yuv);

    //关闭资源
    av_frame_free(&pFrame);
    av_frame_free(&pFrameYUV);
    //关闭编码器上下文
    avcodec_close(pCodecCtx);
    //关闭输入流
    avformat_close_input(&pFormatCtx);
    //关闭封装格式
    avformat_free_context(pFormatCtx);
}

Android Studio编译

注意:创建项目的时候勾选Include C++ support

这里写图片描述

导入所有so库到libs中
这里写图片描述

将include头文件导入cpp目录下
这里写图片描述

修改CMakeLists.txt文件

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.


set(distribution_DIR ${CMAKE_SOURCE_DIR}/libs/)

#libavcodec-56.so
add_library(libavcodec-56-lib SHARED IMPORTED)
set_target_properties(libavcodec-56-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavcodec-56.so)


#libavdevice-56.so
add_library(libavdevice-56-lib SHARED IMPORTED)
set_target_properties(libavdevice-56-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavdevice-56.so)

#libavfilter-5.so
add_library(libavfilter-5-lib SHARED IMPORTED)
set_target_properties(libavfilter-5-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavfilter-5.so)

#libavformat-56.so
add_library(libavformat-56-lib SHARED IMPORTED)
set_target_properties(libavformat-56-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavformat-56.so)

#libavutil-54.so
add_library(libavutil-54-lib SHARED IMPORTED)
set_target_properties(libavutil-54-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libavutil-54.so)


#libpostproc-53.so
add_library(libpostproc-53-lib SHARED IMPORTED)
set_target_properties(libpostproc-53-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libpostproc-53.so)


#libswresample-1.so
add_library(libswresample-1-lib SHARED IMPORTED)
set_target_properties(libswresample-1-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libswresample-1.so)


#libswscale-3.so
add_library(libswscale-3-lib SHARED IMPORTED)
set_target_properties(libswscale-3-lib  PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libswscale-3.so)


add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.c )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
include_directories( src/main/cpp/include/ )
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib}
                       libavcodec-56-lib
                       libavdevice-56-lib
                       libavfilter-5-lib
                       libavformat-56-lib
                       libavutil-54-lib
                       libpostproc-53-lib
                       libswresample-1-lib
                       libswscale-3-lib
                       )

最后代码实现

#include <jni.h>
#include<android/log.h>
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO," FMY",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"FMY",FORMAT,##__VA_ARGS__);
JNIEXPORT void JNICALL
Java_com_example_demoffmpeg_MainActivity_ffmpeg(JNIEnv * env, jobject jobj, jstring input, jstring out) {

    char * input_char = (*env)->GetStringUTFChars(env,input,NULL);

    char *  output_cstr = (*env)->GetStringUTFChars(env,out,NULL);


    //头文件libavformat/avformat.h
    //注册所有组件
    /**
     * 初始化libavformat和注册所有的 muxers, demuxers 和协议,如果你不想使用次函数,
     * 则可以手动选择你想要的支持格式
     * 详情你可以选择下面的函数查询
     * @see av_register_input_format()
     * @see av_register_output_format()
     *
     * muxer是合并将视频文件、音频文件和字幕文件合并为某一个视频格式。如,可将a.avi, a.mp3, a.srt用muxer合并为mkv格式的视频文件。
     * demuxer是拆分这些文件的。
     */
    av_register_all();

    // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。
    AVFormatContext * pFormatCtx = avformat_alloc_context();

    /**
     * 打开输入流并且读取头信息。但解码器没有打开
     * 这个输入流必须使用avformat_close_input()关闭
     * @param ps(第一个参数的形参名称) 指向 你由你提供AVFormatContext(AVFormatContext是由avformat_alloc_context函数分配的)。
     * 有可能ps指向空,在这种情况下,AVFormatContext由此函数分配并写入ps。
     * 注意: 你提供的AVFormatContext在函数执行失败的时候将会被释放
     * @param url 你要打开视频文件路径.
     * @param fmt  如果不为空,那么这个参数将强制作为输入格式,否则自动检索
     * @param options 一个关于AVFormatContext and demuxer-private 选项的字典.
     * 返回时,此参数将被销毁,并替换为包含未找到的选项的dict。有可能是空的
     *
     * @return 返回0表示成功, 一个负数常量AVERROR是失败的.
     *
     * @note 如果你想自定义IO,你需要预分配格式内容并且设置pd属性
     */
    if(avformat_open_input(&pFormatCtx,input_char,NULL,NULL)!=0){
        LOGE("NDK>>>%s","avformat_open_input打开失败");
        return;
    }

    //上面打开输入流后会将视频封装格式信息写入AVFormatContext中那么我们下一步就可以得到一些展示信息
    /**
     *
     * 读取媒体文件中的数据包以获取流信息,这个对于对于文件格式没有头信息的很有帮助,比如说mpeg
     * 这个函数还可以计算在MPEG-2重复帧模式的真实帧速率。
     * 逻辑文件位置不会被这个函数改变
     * 检索过的数据包或许会缓存以供后续处理
     * @param ic  第一个参数 封装格式上下文
     * @param options
     *              如果不为空, 一个长度为 ic.nb_streams (封装格式所有流,字幕 视频 音频等) 的字典。
     *              字典中第i个成员  包含一个对应ic第i个流中对的编码器。
     *              在返回时,每个字典将会填充没有找到的选项
     * @return 如果返回>=0 代表成功, AVERROR_xxx 表示失败
     *
     * @note 这个函数 不保证能打开所有编码器,所以返回一个非空的选项是一个完全正常的行为
     *
     *
     * @todo
     *  下个版本目标无视即可
     * Let the user decide somehow what information is needed so that
     *       we do not waste time getting stuff the user does not need.
     */
    if( avformat_find_stream_info(pFormatCtx,NULL)<0){
        LOGE("NDK>>>%s","avformat_find_stream_info失败");
        return ;
    }
    LOGE("NDK>>>%s","成功");
    //  //输出视频信息
    //  LOGI("视频的文件格式:%s",pFormatCtx->iformat->name);
    //  LOGI("视频时长:%d", (pFormatCtx->duration)/1000000);

    //获取视频流的索引位置
    //遍历所有类型的流(音频流、视频流、字幕流),找到视频流
    int v_stream_idx = -1;
    int i = 0;
    //遍历封装格式中所有流
    for (; i < pFormatCtx->nb_streams; ++i) {

        //获取视频流pFormatCtx->streams[i]
        //pFormatCtx->streams[i]->codec获取编码器
        //codec_type获取编码器类型
        //当前流等于视频 记录下标
        if (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_VIDEO) {
            v_stream_idx = i;
            break;
        }
    }
    if (v_stream_idx==-1) {
        LOGE("没有找视频流")
    }else{
        LOGE("找到视频流")
    }

    //编码器上下文结构体,保存了视频(音频)编解码相关信息
    //得到视频流编码器
    AVCodecContext *pCodecCtx = pFormatCtx->streams[v_stream_idx]->codec;

    //   每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
    AVCodec *pCodec =avcodec_find_decoder(pCodecCtx->codec_id);

    //(迅雷看看,找不到解码器,临时下载一个解码器)
    if (pCodec == NULL)
    {
        LOGE("%s","找不到解码器\n");
        return;
    }else{
        LOGE("%s","找到解码器\n");
    }


    //打开解码器
    /**
     * 初始化 指定AVCodecContext去使用 给定的AVCodec
     * 在使用之前函数必须使用avcodec_alloc_context3()分配上下文。
     *
     * 以下函数 avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(),
     * avcodec_find_decoder() and avcodec_find_encoder() 提供了一个简便的得到一个解码器的方法
     *
     * @warning 这个函数线程不是安全的
     *
     * @note 在使用解码程序之前,始终调用此函数 (如 @ref avcodec_decode_video2()).
     * 下面是示例代码
     * @code
     * avcodec_register_all();
     * av_dict_set(&opts, "b", "2.5M", 0);
     * codec = avcodec_find_decoder(AV_CODEC_ID_H264);
     * if (!codec)
     *     exit(1);
     *
     * context = avcodec_alloc_context3(codec);
     *
     * if (avcodec_open2(context, codec, opts) < 0)
     *     exit(1);
     * @endcode
     *
     *
     * @param avctx 要初始化的编码器
     * @param codec 用这个codec去打开给定的上下文编码器.如果 codec 不为空 那么必须
     * 事先用avcodec_alloc_context3和avcodec_get_context_defaults3传递给这个context,那么这个codec
     * 要么为NULL要么就是上面调用函数所使用的codec
     *
     * @param
     *
     * 选项填充AVCodecContext和编解码器私有选项的字典。返回时,此对象将填充未找到的选项。
     *
     * @return 返回0表示成功, 负数失败
     * @see avcodec_alloc_context3(), avcodec_find_decoder(), avcodec_find_encoder(),
     *      av_dict_set(), av_opt_find().
     */
    if(avcodec_open2(pCodecCtx,pCodec,NULL)==0){
        LOGE("%s","打开编码器成功\n");
    }else{
        LOGE("%s","打开编码器失败\n");
        return;
    }
    //输出视频信息
    LOGE("视频的文件格式:%s",pFormatCtx->iformat->name);
    //得到视频播放时长
    if(pFormatCtx->duration != AV_NOPTS_VALUE){
        int hours, mins, secs, us;
        int64_t duration = pFormatCtx->duration + 5000;
        secs = duration / AV_TIME_BASE;
        us = duration % AV_TIME_BASE;
        mins = secs / 60;
        secs %= 60;
        hours = mins/ 60;
        mins %= 60;
        LOGE("%02d:%02d:%02d.%02d\n", hours, mins, secs, (100 * us) / AV_TIME_BASE);

    }
    LOGE("视频的宽高:%d,%d",pCodecCtx->width,pCodecCtx->height);
    LOGE("解码器的名称:%s",pCodec->name);


    //存储一帧压缩编码数据。
    AVPacket *packet =av_malloc(sizeof(AVPacket));

    //输出转码文件地址
    FILE *fp_yuv = fopen(output_cstr,"wb+");

    //AVFrame用于存储解码后的像素数据(YUV)
    //内存分配
    AVFrame *pFrame = av_frame_alloc();

    //YUV420转码用
    AVFrame *pFrameYUV = av_frame_alloc();

    //avpicture_get_size()函数介绍:
    //
    /**
     * 如果给定存储图片的格式,那么计算给定的宽高所占用的大小
     *
     * @param pix_fmt   图片像素格式
     * @param width     图片宽
     * @param height     图片高
     * @return 返回计算的图片缓存大小或者错误情况下的负数错误代码
     *
     *
     * 这里计算缓存区的大小,但是没有分配,这里是用来后面转码使用
     */
    uint8_t *out_buffer = av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height));

    //初始化缓冲区
    /**
     * 基于指定的图片参数和提供的图片缓存区去设置图片字段
     *
     * 使用ptr所指向的图片数据缓存  填充图片属性
     *
     * 如果 ptr是空,这个函数仅填充图片行大小(linesize)的数组并且返回图片缓存请求的大小
     *
     * 要分配图片缓存并且再一次填充图片数据请使用 avpicture_alloc().
     * @param picture       要填充的图片
     * @param ptr           存储图片的数据的缓存区, or NULL
     * @param pix_fmt       图片像素格式
     * @param width         图片宽
     * @param height        图片高
     * @return 返回请求的字节大小,在错误的情况下返回负数
     *
     * @see av_image_fill_arrays()
     */
    avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
    //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
    /**
     *分配和返回 SwsContext. You need it to perform
     * scaling/conversion operations using sws_scale().
     *
     * @param srcW 原始图宽
     * @param srcH 原始图高
     * @param srcFormat 原始图格式
     * @param dstW 目标图宽
     * @param dstH 不解释
     * @param dstFormat 不解释
     * @param flags 指定一个标志用于重新调整算法和选项
     *  具体参考:http://blog.csdn.net/leixiaohua1020/article/details/12029505
     * @return 一个指定分配内容的指针, 错误情况返回空
     * @note this function is to be removed after a saner alternative is
     *       written
     */
    struct SwsContext *sws_ctx =sws_getContext(pCodecCtx->width, pCodecCtx->height,pCodecCtx->pix_fmt,
                                               pCodecCtx->width, pCodecCtx->height,AV_PIX_FMT_YUV420P,
                                               SWS_BICUBIC, NULL, NULL, NULL);


    //标志位
    int got_picture, ret;

    //读取每一帧
    /**
     *返回下一帧的流
     * 此函数返回存储在文件中的内容,并且不会验证解码器有什么有效帧。
     * 函数将存储在文件中的帧进行分割 并且返回给每一个调用者。
     *
     * 函数不会删除在有效帧之间的无效数据 以便在可能解码过程中提供解码器最大的信息帮助
     * 如果 pkt->buf 是空的,那么这个对应数据包是有效的直到下一次调用av_read_frame()
     * 或者直到使用avformat_close_input().否则包无期限有效
     * 在这两种情况下 这个数据包当你不在需要的时候,你必须使用使用av_free_packet释放它
     * 对于视屏,数据包刚好只包含一帧.对于音频,如果它每一帧是一个已知固定大小的,那么他包含整数帧(如. PCM or ADPCM data)
     * 如果音频帧具有可变大小(如. MPEG audio),那么他只包含一帧
     * pkt->pts, pkt->dts and pkt->duration 始终在AVStream.time_base 单位设置正确的数值
     *(如果这个格式无法提供.那么进行猜测)
     * 如果视频格式有B帧那么pkt->pts可以是 AV_NOPTS_VALUE.如果你没有解压他的有效部分那么最好依靠pkt->dts
     *
     * @return 0表示成功, < 0 错误或者文结束
     */
    while(av_read_frame(pFormatCtx,packet)>=0){

        //一个包里有很多种类型如音频视频等 所以判断 这个包对应的流的在封装格式的下表
        //如果这个包是视频频包那么得到压缩的视频包
        if (packet->stream_index==v_stream_idx) {
            LOGE("测试");
            /**
             * 解码视频帧 从avpkt->data读取数据并且解码avpkt->size的大小后转化为图片.
             * 一些解码器可以支持在一个ACpacket中存在多帧的情况,像这样的解码器将只解码第一帧
             *
             * @warning  输入缓存区必须 实际读取的字节流小于 FF_INPUT_BUFFER_PADDING_SIZE,
             * 一些优化过的比特流 读取32位或者64字节 的时候可以一次性读取完
             *
             * @warning 在缓冲器的buf结尾设置0以确保被破坏的MPEG流不会发生超线程
             *
             * @note 有 CODEC_CAP_DELAY 才能设置一个在输入和输出之间的延迟,这些需要使用avpkt->data=NULL,
             *  在结束返回剩余帧数的时候avpkt->size=0
             *
             * @note  这个AVCodecContext 在数据包传入解码器之前必须调用avcodec_open2
             *
             *
             * @param avctx 解码器上下文
             *
             * @param[out] 解码的视频帧图片将会被存储在AVFrame.
             *                 使用av_frame_alloc 得到一个AVFrame,
             *                 编码器将会分配 使用  AVCodecContext.get_buffer2() 回调
             *                 的实际图片的内存.
             *                 当AVCodecContext.refcounted_frames 设置为1,这帧(frame)是引用计数,并且返回
             *                 的引用计数是属于调用者的.
             *             frame在长实际不使用的时候调用者必须调用av_frame_unref()就行释放
             *             如果av_frame_is_writable()返回1那么调用者可以安全的写入到这个frame中。
             *                 当AVCodecContext.refcounted_frames设置为0,返回的引用属于解码器,
             *                只有下次使用这个函数或者关闭或者刷新这个编码器之前有效。调用者不会写入它
             *
             *@param[in,out] got_picture_ptr 如果为0表示不能解压, 否者它不是0.
             *
             * @param[in] avpkt 这个输入的avpkt包含输入缓存区
             *              你能使用av_init_packet()创建像这样的packet然后设置数据和大小,
             *              一些解码器还可以添加一些其他字段 比如  flags&AV_PKT_FLAG_KEY  flags&AV_PKT_FLAG_KEY
             *          所有解码器都设计为尽可能少地使用
             *
             * @return 再错误时返回一个负数 , 否则返回使用字节数或者或者0(没有帧被解压返回0)otherwise the number of bytes
             *
             */
            ret=avcodec_decode_video2(pCodecCtx,pFrame,&got_picture,packet);
            if(ret>=0){
                LOGE("解压成功");
                //AVFrame转为像素格式YUV420,宽高
                //2 6输入、输出数据
                //3 7输入、输出画面一行的数据的大小 AVFrame 转换是一行一行转换的
                //4 输入数据第一列要转码的位置 从0开始
                //5 输入画面的高度
                sws_scale(sws_ctx,pFrame->data,pFrame->linesize,0,pCodecCtx->height,pFrameYUV->data,pFrameYUV->linesize);

                //输出到YUV文件
                //AVFrame像素帧写入文件
                //data解码后的图像像素数据(音频采样数据)
                //Y 亮度 UV 色度(压缩了) 人对亮度更加敏感
                //U V 个数是Y的1/4
                int y_size = pCodecCtx->width * pCodecCtx->height;

                fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);
                fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv);
                fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv);

            }
        }
        av_free_packet(packet);
    }


    (*env)->ReleaseStringUTFChars(env,input,input_char);

    (*env)->ReleaseStringUTFChars(env,out,output_cstr);
    //关闭文件
    fclose(fp_yuv);

    //关闭资源
    av_frame_free(&pFrame);
    av_frame_free(&pFrameYUV);
    //关闭编码器上下文
    avcodec_close(pCodecCtx);
    //关闭输入流
    avformat_close_input(&pFormatCtx);
    //关闭封装格式
    avformat_free_context(pFormatCtx);
}

Studio版本DEMO

2
0
查看评论

【FFMEPG】windows下编译ffmpeg2.5——使用VS2013,ARMLINUX,ANDORID编译ffmpeg

原文:http://blog.csdn.net/finewind/article/details/42784557 一、准备: 1. 本机环境: win7 64bit; 2. 安装MinGW到C:\MinGW,下载地址http://www.mingw.org/; 3. 安装y...
  • LG1259156776
  • LG1259156776
  • 2016-11-13 16:04
  • 399

FFmpeg时间戳详解。 解码编码

对于时间戳一直不懂。 偶然看到这篇博客,写得很好。  转自:http://blog.chinaunix.net/uid-26000296-id-3483782.html 一、FFmpeg忽略了adaptation_field()数据 FFmpeg忽略了包含PCR值的adaptati...
  • asb2010
  • asb2010
  • 2015-01-23 14:17
  • 1260

ffmpeg 2.1 支持YUVJ422P and YUVJ444P编码了(libx264),最新版的Live555支持H265了,看来H265马上就大范围用了!

We have made a new major release (2.1) It contains all features and bugfixes of the git master branch from 28th October. A partial list of new stuff i...
  • smilestone322
  • smilestone322
  • 2013-12-27 10:56
  • 1925

ffmeg

音频文件转码简介本文描述如何从其它格式的音频转成符合语音识别输入要求格式的音频文件。由于底层识别使用的是pcm,因此推荐直接上传pcm文件。如果上传其它格式,会在服务器端转码成pcm,调用接口的耗时会增加。语音识别仅支持以下格式 :pcm(不压缩)、wav(不压缩,pcm编码)、amr(有损压缩格式...
  • mezheng
  • mezheng
  • 2018-01-18 16:42
  • 31

基于NVIDIA显卡的硬编解码的一点心得 (完结)

1.硬解码软编码方法:大体流程,先用ffmpeg来读取视频文件的包,接着开启两个线程,一个用于硬解码,一个用于软编码,然后将读取的包传给解码器,编码出的frame download到内存,然后做scale处理,将scale后的帧和编码参数一起传给编码函数,最终生成pkt包,将其写入文件。由于CUVI...
  • u010264950
  • u010264950
  • 2014-11-07 09:53
  • 6312

FFMpeg视频解码+OpenCV显示

虽然OpenCV底层的视频解码也是用的FFMpeg,但是自带的FFMpeg版本过低,其实OpenCV本来就是一个计算机视觉的库,不是用来进行视频开发的,所以最好是单独用FFMpeg进行视频编解码,对FFMpeg解码得到的帧图像利用OpenCV进行处理。参考了网上的一些资料,总结了一下FFMpeg与O...
  • gechengweisky
  • gechengweisky
  • 2013-08-30 16:38
  • 5688

unbutu下ffmpeg安装

unbutu下ffmpeg安装FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcode...
  • u013057233
  • u013057233
  • 2016-05-09 10:35
  • 620

FFmeg快速入门

FFmpeg简介 FFmpeg是什么? FFmpeg是用于录制、转换和流化音频和视频的完整解决方案, 包括 libavcodec ,一套领先的音/视频编解码类库。FFmpeg 在Linux上开发,当可以在大多数操作系统下编译,包括Windows。 Note FFmpeg对GCC的依赖很强,所以就...
  • muzhengjun
  • muzhengjun
  • 2015-07-14 17:14
  • 922

安卓 URL编码 和解码注意事项

例如URL: http://baidu.com/html?key=美泰&sort=0&districtId=1&page=0&size=40&sceneId=1&siteId=1&sourceId=1&userId=0 1....
  • rnZuoZuo
  • rnZuoZuo
  • 2015-11-14 15:25
  • 1007

FFmpeg中的滤镜(九):视频滤镜 -- blackframe

blackframe 描述 检测全黑的视频帧。在检查视频中的过渡片段、广告等黑屏画面时很有效。输出包括被检查帧的帧号、视频帧中黑色的百分比、视频帧(此处存疑,待验证。TODO)在文件中的位置(若位置不可知,则值为-1)、视频帧的时间戳(精确到s)。若要显示输出,最低要...
  • joee33
  • joee33
  • 2016-07-27 22:57
  • 464
    个人资料
    • 访问:491600次
    • 积分:6793
    • 等级:
    • 排名:第4126名
    • 原创:274篇
    • 转载:63篇
    • 译文:0篇
    • 评论:51条