对于想了解使用ffmpeg从图像创建视频文件的读者,本文将是一篇不可错过的文章,我们将详细介绍ffmpeg图片生成视频,并且为您提供关于Android使用FFmpeg(一)--编译ffmpeg、And
对于想了解使用ffmpeg从图像创建视频文件的读者,本文将是一篇不可错过的文章,我们将详细介绍ffmpeg图片生成视频,并且为您提供关于Android使用FFmpeg(一)--编译ffmpeg、Android使用FFmpeg(三)--ffmpeg实现视频播放、Android使用FFmpeg(六)--ffmpeg实现音视频同步播放、c# 用ffmpeg从视频中截图的有价值信息。
本文目录一览:- 使用ffmpeg从图像创建视频文件(ffmpeg图片生成视频)
- Android使用FFmpeg(一)--编译ffmpeg
- Android使用FFmpeg(三)--ffmpeg实现视频播放
- Android使用FFmpeg(六)--ffmpeg实现音视频同步播放
- c# 用ffmpeg从视频中截图
使用ffmpeg从图像创建视频文件(ffmpeg图片生成视频)
我现在可以编译ffmpeg并将其添加到项目创建的Android.mk文件的jni文件夹中,我想使用ffmpeg从存储在静态arraylist中的图像创建视频文件
我已经搜索了很多,但找不到任何教程,对此表示感谢。
答案1
小编典典我也有类似的需求,并且达到了相同的目的。您可以通过两种方式执行此操作。我想先分享一个简单的例子。
在Android内部创建一个临时文件夹。
将图像复制到新文件夹中
首先,将图片重命名以遵循数字顺序。例如,img1.jpg,img2.jpg,img3.jpg…。然后,您可以运行:
以编程方式运行此程序
ffmpeg -f image2 -i img%d.jpg /tmp/a.mpg
要以编程方式运行此程序,
使用以下代码:
void convertImg_to_vid(){ Process chperm; try { chperm=Runtime.getRuntime().exec("su"); DataOutputStream os = new DataOutputStream(chperm.getOutputStream()); os.writeBytes("ffmpeg -f image2 -i img%d.jpg /tmp/a.mpg\n"); os.flush(); chperm.waitFor(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }}
开始吧。我会帮助你的。
使用本教程:http : //ffmpeg.org/faq.html 本教程中的“
特别介绍3.2”部分。
为了能够运行上述命令,您应该在bin目录中有ffmpeg命令。ffmpeg二进制文件应针对Android平台交叉编译…
Android使用FFmpeg(一)--编译ffmpeg
关于
Android使用FFmpeg(一)--编译ffmpeg
Android使用FFmpeg(二)--Android Studio配置ffmpeg
Android使用FFmpeg(三)--ffmpeg实现视频播放
Android使用FFmpeg(四)--ffmpeg实现音频播放(使用AudioTrack进行播放)
Android使用FFmpeg(五)--ffmpeg实现音频播放(使用openSL ES进行播放)
Android使用FFmpeg(六)--ffmpeg实现音视频同步播放
Android使用FFmpeg(七)--ffmpeg实现暂停、快退快进播放
前言
ffmpeg简介
在现今这个阶段,越来越多的app会涉及到音视频,那么学会使用ffmpeg就很有必要了。在这个系类中将讲解如何把ffmpeg编译成动态库,以及使用ffmpeg实现音视频播放和音视频的采集。
编译环境
Linux/Ubuntu/centos都行
在windows环境下可以安装虚拟机或者购买一个云主机,作者在金山云购买的一个Ubuntu主机。
准备工作
下载配置ndk,下载ffmpeg并解压。
开始
配置ndk
如果你已经配置ok,请跳过这一步。
我们打开自己的虚拟机或者云服务器并且获取到root权限。创建文件夹ndk_build并进入到文件夹中,下载ndk,并解压
mkdir ndk_build//新建文件夹
cd ndk_build//进入到文件夹
wget +linux版本的下载链接地址//下载ndk
unzip + 压缩文件//解压
配置ndk
vim ~/.bashrc//进入环境变量配置
export NDKROOT=/home/ndk_build/android-ndk-r14b//配置你的安装路径
export PATH=$NDKROOT:$PATH//配置路径
:wq!//保存退出
source ~/.bashrc//执行环境变量
ndk-build//查看是否安装成功,只要不是显示ndk-build not found,则表示安装成功
下载编译ffmpeg
下载ffmpeg并解压
下载ffmpeg并解压.png
wget http://ffmpeg.org/releases/ffmpeg-2.6.9.tar.gz
tar -xzf ffmpeg-2.6.9.tar.gz
修改configure文件并新建android_build.sh文件,编辑android_build.sh
#!/bin/bash
make clean
export NDK=/home/ndk_build/android-ndk-r14b
export SYSROOT=$NDK/platforms/android-9/arch-arm/
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/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是你自己刚刚配置的ndk路径,SYSROOT为platforms中的路径,TOOLCHAIN为toolchains里面中的路径,依次配置好过后保存并退出。
修改configure,如果你不修改的话,编译出来过后的.so文件后面会有一串数字,无法使用,所以得修改他的命名规则。
将该文件中的如下四行:
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)''
编译.sh文件
···
./android_build.sh
···
当编译成功过后会在文件夹中出现android文件夹,
在lib文件夹中就有你需要的.so文件了
这样就差不多编译完成,如果你需要编译一份别人已经编译过的ffmpeg,那么请先执行./confifure --disable-yasm,然后再自己编译。
如果还没编译好但是急需的朋友可以直接下载使用。
so下载地址
Android使用FFmpeg(三)--ffmpeg实现视频播放
前言
如果你已经准备好ffmpeg的开发环境,那么我们在这篇文章中实现对视频的一个播放,如果还没有准备好,请看前面的内容。
正文
Ok,上图就是使用ffmpeg实现了一个视频的播放的大概流程图,那么,我们将根据流程图来编写代码,这样子,代码的编写就会显得比较简单,比较好理解了。
1.注册各大组件,这一步很重要,如果不注册就无法使用后面的函数了。
av_register_all();
2.在解码之前我们得获取里面的内容吧,所以这一步就是打开地址并且获取里面的内容。其中avFormatContext是内容的一个上下文,inputPath为输入的地址。
AVFormatContext *avFormatContext = avformat_alloc_context();//获取上下文
avformat_open_input(&avFormatContext, inputPath, NULL, NULL)//解封装
avformat_find_stream_info(avFormatContext, NULL)
3.我们在上面已经获取了内容,但是在一个音视频中包括了音频流,视频流和字幕流,所以在所有的内容当中,我们应当找出相对应的视频流。
int video_index=-1;
for (int i = 0; i < avFormatContext->nb_streams; ++i) {
if (avFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
//如果是视频流,标记一哈
video_index = i;
}
}
4.在第三步的时候已经找到了视频流,那么我们就对视频流进行解码、转换和绘制。
a.如果要进行解码,那么得有解码的装置并打开解码的装置。
//获取解码的装置上下文
AVCodecContext *avCodecContext = avFormatContext->streams[video_index]->codec;
//获取解码的装置
AVCodec *avCodec = avcodec_find_decoder(avCodecContext->codec_id);
//打开解码的装置
if (avcodec_open2(avCodecContext, avCodec, NULL) < 0) {
LOGE("打开失败")
return;
}
b.申请AVPacket和AVFrame,其中AVPacket的作用是:保存解码之前的数据和一些附加信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等;AVFrame的作用是:存放解码过后的数据。
具体可参考:http://blog.csdn.net/leixiaohua1020/article/details/11693997
//申请AVPacket
AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
av_init_packet(packet);
//申请AVFrame
AVFrame *frame = av_frame_alloc();//分配一个AVFrame结构体,AVFrame结构体一般用于存储原始数据,指向解码后的原始帧
AVFrame *rgb_frame = av_frame_alloc();//分配一个AVFrame结构体,指向存放转换成rgb后的帧
c.因为rgb_frame是一个缓存区域,所以需要设置。
//缓存区
uint8_t *out_buffer= (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_RGBA,
avCodecContext->width,avCodecContext->height));
//与缓存区相关联,设置rgb_frame缓存区
avpicture_fill((AVPicture *)rgb_frame,out_buffer,AV_PIX_FMT_RGBA,avCodecContext->width,avCodecContext->height);
d.因为是原生绘制,即是说需要ANativeWindow。
//取到nativewindow
ANativeWindow *nativeWindow=ANativeWindow_fromSurface(env,surface);
if(nativeWindow==0){
LOGE("nativewindow取到失败")
return;
}
//视频缓冲区
ANativeWindow_Buffer native_outBuffer;
e.一切准备妥当,那么我们开始解码。
while (av_read_frame(avFormatContext, packet) >= 0) {
LOGE("解码 %d",packet->stream_index)
LOGE("VINDEX %d",video_index)
if(packet->stream_index==video_index){
LOGE("解码 hhhhh")
//如果是视频流
//解码
avcodec_decode_video2(avCodecContext, frame, &frameCount, packet)
}
av_free_packet(packet);
}
f.以下均在循环里面进行,当解码一帧成功过后,我们转换成rgb格式并且绘制。
if (frameCount) {
LOGE("转换并绘制")
//说明有内容
//绘制之前配置nativewindow
ANativeWindow_setBuffersGeometry(nativeWindow,avCodecContext->width,avCodecContext->height,WINDOW_FORMAT_RGBA_8888);
//上锁
ANativeWindow_lock(nativeWindow, &native_outBuffer, NULL);
//转换为rgb格式
sws_scale(swsContext,(const uint8_t *const *)frame->data,frame->linesize,0,
frame->height,rgb_frame->data,
rgb_frame->linesize);
// rgb_frame是有画面数据
uint8_t *dst= (uint8_t *) native_outBuffer.bits;
// 拿到一行有多少个字节 RGBA
int destStride=native_outBuffer.stride*4;
//像素数据的首地址
uint8_t * src= rgb_frame->data[0];
// 实际内存一行数量
int srcStride = rgb_frame->linesize[0];
//int i=0;
for (int i = 0; i < avCodecContext->height; ++i) {
// memcpy(void *dest, const void *src, size_t n)
//将rgb_frame中每一行的数据复制给nativewindow
memcpy(dst + i * destStride, src + i * srcStride, srcStride);
}
//解锁
ANativeWindow_unlockAndPost(nativeWindow);
usleep(1000 * 16);
}
在上面的代码中,因为转换成rgb格式过后的内容是存在ffmpeg所指向的地址而不是ANativeWindow所指向的所在地址,所以要绘制的话我们需要将内容复制到ANativeWindow中。
5.完成过后得释放资源,不然就造成内存泄露了。
ANativeWindow_release(nativeWindow);
av_frame_free(&frame);
av_frame_free(&rgb_frame);
avcodec_close(avCodecContext);
avformat_free_context(avFormatContext);
env->ReleaseStringUTFChars(inputStr_, inputPath);
6.java层代码,因为是原生绘制,所以需要传入Surface,所以创建一个类继承SurfaceView。
class VideoView : SurfaceView {
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
private fun init() {
val holder = holder
holder.setFormat(PixelFormat.RGBA_8888)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
fun player(input: String) {
Thread(Runnable {
// 绘制功能 不需要交给SurfaveView VideoView.this.getHolder().getSurface()
render(input, this@VideoView.holder.surface)
}).start()
}
external fun render(input: String, surface: Surface)
companion object {
init {
System.loadLibrary("avcodec-56")
System.loadLibrary("avdevice-56")
System.loadLibrary("avfilter-5")
System.loadLibrary("avformat-56")
System.loadLibrary("avutil-54")
System.loadLibrary("postproc-53")
System.loadLibrary("swresample-1")
System.loadLibrary("swscale-3")
System.loadLibrary("native-lib")
}
}
}
小结
以上就是对视频的解封装,解码,转换,绘制的一个过程,过程清晰明了,按着这个步奏来就应该来说比较简单,另外,请在真机上测试,同时导入自己想测试的视频,什么格式都可以。
Android使用FFmpeg(六)--ffmpeg实现音视频同步播放
关于
Android使用FFmpeg(一)--编译ffmpeg
Android使用FFmpeg(二)--Android Studio配置ffmpeg
Android使用FFmpeg(三)--ffmpeg实现视频播放
Android使用FFmpeg(四)--ffmpeg实现音频播放(使用AudioTrack进行播放)
Android使用FFmpeg(五)--ffmpeg实现音频播放(使用openSL ES进行播放)
Android使用FFmpeg(六)--ffmpeg实现音视频同步播放
Android使用FFmpeg(七)--ffmpeg实现暂停、快退快进播放
准备工作
Android使用FFmpeg(三)--ffmpeg实现视频播放
Android使用FFmpeg(五)--ffmpeg实现音频播放(使用openSL ES进行播放)
同步原理介绍
正文
依旧依照流程图来逐步实现同步播放:
从流程图可以看出,实现同步播放需要三个线程,一个开启解码的装置得到packet线程,然后分别是播放音频和视频的线程。这篇简书是以音频播放为基准来进行播放,也就是音频一直不停的播放,视频根据音频播放来调整延迟时间。
1.开启play线程,在这个线程中,注册组件,得到音视频的解码的装置并将packet压入队列。这里和前面的音视频分开播放并没有多大差别,也就多了一个队列。
void *begin(void *args) {
LOGE("开启解码线程")
//1.注册组件
av_register_all();
avformat_network_init();
//封装格式上下文
AVFormatContext *pFormatCtx = avformat_alloc_context();
//2.打开输入视频文件
if (avformat_open_input(&pFormatCtx, inputPath, NULL, NULL) != 0) {
LOGE("%s", "打开输入视频文件失败");
}
//3.获取视频信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
LOGE("%s", "获取视频信息失败");
}
//找到视频流和音频流
int i=0;
for (int i = 0; i < pFormatCtx->nb_streams; ++i) {
//获取解码的装置
AVCodecContext *avCodecContext = pFormatCtx->streams[i]->codec;
AVCodec *avCodec = avcodec_find_decoder(avCodecContext->codec_id);
//copy一个解码的装置,
AVCodecContext *codecContext = avcodec_alloc_context3(avCodec);
avcodec_copy_context(codecContext,avCodecContext);
if(avcodec_open2(codecContext,avCodec,NULL)<0){
LOGE("打开失败")
continue;
}
//如果是视频流
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
ffmpegVideo->index=i;
ffmpegVideo->setAvCodecContext(codecContext);
ffmpegVideo->time_base=pFormatCtx->streams[i]->time_base;
if (window) {
ANativeWindow_setBuffersGeometry(window, ffmpegVideo->codec->width, ffmpegVideo->codec->height,
WINDOW_FORMAT_RGBA_8888);
}
}//如果是音频流
else if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){
ffmpegMusic->index=i;
ffmpegMusic->setAvCodecContext(codecContext);
ffmpegMusic->time_base=pFormatCtx->streams[i]->time_base;
}
}
//开启播放
ffmpegVideo->setFFmepegMusic(ffmpegMusic);
ffmpegMusic->play();
ffmpegVideo->play();
isPlay=1;
//读取packet,并压入队列中
AVPacket *packet = (AVPacket *)av_mallocz(sizeof(AVPacket));
int ret;
while (isPlay){
ret = av_read_frame(pFormatCtx,packet);
if(ret ==0){
if(ffmpegVideo&&ffmpegVideo->isPlay&&packet->stream_index==ffmpegVideo->index){
//将视频packet压入队列
ffmpegVideo->put(packet);
} else if(ffmpegMusic&&ffmpegMusic->isPlay&&packet->stream_index==ffmpegMusic->index){
ffmpegMusic->put(packet);
}
av_packet_unref(packet);
} else if(ret ==AVERROR_EOF){
while (isPlay) {
if (vedio->queue.empty() && audio->queue.empty()) {
break;
}
// LOGI("等待播放完成");
av_usleep(10000);
}
}
}
//读取完过后可能还没有播放完
isPlay=0;
if(ffmpegMusic && ffmpegMusic->isPlay){
ffmpegMusic->stop();
}
if(ffmpegVideo && ffmpegVideo->isPlay){
ffmpegVideo->stop();
}
//释放
av_free_packet(packet);
avformat_free_context(pFormatCtx);
}
2.因为音频播放就是一直播放,所以音频播放和单独的音频播放并没有多大差别,只是多了一个时间的记录。其中,因为pakcet是开始压入队列,然后再从队列中取出,当注意到线程安全,此处使用生产者消费者模式,也就是说当队列中有了pakcet过后才能从中取出,不然的话就等待。
//生产者
int FFmpegAudio::put(AVPacket *packet) {
AVPacket *packet1 = (AVPacket *) av_mallocz(sizeof(AVPacket));
if (av_packet_ref(packet1, packet)) {
// 克隆失败
return 0;
}
pthread_mutex_lock(&mutex);
queue.push(packet1);
LOGE("压入一帧音频数据 队列%d ",queue.size());
// 给消费者解锁
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return 1;
}
//消费者
int FFmpegAudio::get(AVPacket *packet) {
pthread_mutex_lock(&mutex);
while (isPlay) {
if (!queue.empty()) {
// 从队列取出一个packet clone一个 给入参对象
if (av_packet_ref(packet, queue.front())) {
break;
}
// 取成功了 弹出队列 销毁packet
AVPacket *pkt = queue.front();
queue.pop();
LOGE("取出一 个音频帧%d",queue.size());
av_free(pkt);
break;
} else {
// 如果队列里面没有数据的话 一直等待阻塞
pthread_cond_wait(&cond, &mutex);
}
}
pthread_mutex_unlock(&mutex);
return 0;
}
//时间计算
//回调函数
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
//得到pcm数据
LOGE("回调pcm数据")
FFmpegMusic *musicplay = (FFmpegMusic *) context;
int datasize = getPcm(musicplay);
if(datasize>0){
//第一针所需要时间采样字节/采样率
double time = datasize/(44100*2*2);
//
musicplay->clock=time+musicplay->clock;
LOGE("当前一帧声音时间%f 播放时间%f",time,musicplay->clock);
(*bq)->Enqueue(bq,musicplay->out_buffer,datasize);
LOGE("播放 %d ",musicplay->queue.size());
}
}
//时间矫正
if (avPacket->pts != AV_NOPTS_VALUE) {
agrs->clock = av_q2d(agrs->time_base) * avPacket->pts;
}
3.视频播放线程,因为视频是根据播放的时间来进行一个加速或者延迟播放的,所以对时间的计算是很重要的。
void *videoPlay(void *args){
FFmpegVideo *ffmpegVideo = (FFmpegVideo *) args;
//申请AVFrame
AVFrame *frame = av_frame_alloc();//分配一个AVFrame结构体,AVFrame结构体一般用于存储原始数据,指向解码后的原始帧
AVFrame *rgb_frame = av_frame_alloc();//分配一个AVFrame结构体,指向存放转换成rgb后的帧
AVPacket *packet = (AVPacket *) av_mallocz(sizeof(AVPacket));
//输出文件
//FILE *fp = fopen(outputPath,"wb");
//缓存区
uint8_t *out_buffer= (uint8_t *)av_mallocz(avpicture_get_size(AV_PIX_FMT_RGBA,
ffmpegVideo->codec->width,ffmpegVideo->codec->height));
//与缓存区相关联,设置rgb_frame缓存区
avpicture_fill((AVPicture *)rgb_frame,out_buffer,AV_PIX_FMT_RGBA,ffmpegVideo->codec->width,ffmpegVideo->codec->height);
LOGE("转换成rgba格式")
ffmpegVideo->swsContext = sws_getContext(ffmpegVideo->codec->width,ffmpegVideo->codec->height,ffmpegVideo->codec->pix_fmt,
ffmpegVideo->codec->width,ffmpegVideo->codec->height,AV_PIX_FMT_RGBA,
SWS_BICUBIC,NULL,NULL,NULL);
LOGE("LC XXXXX %f",ffmpegVideo->codec);
double last_play //上一帧的播放时间
,play //当前帧的播放时间
,last_delay // 上一次播放视频的两帧视频间隔时间
,delay //两帧视频间隔时间
,audio_clock //音频轨道 实际播放时间
,diff //音频帧与视频帧相差时间
,sync_threshold
,start_time //从第一帧开始的绝对时间
,pts
,actual_delay//真正需要延迟时间
;
//两帧间隔合理间隔时间
start_time = av_gettime() / 1000000.0;
int frameCount;
int h =0;
LOGE("解码 ")
while (ffmpegVideo->isPlay) {
ffmpegVideo->get(packet);
LOGE("解码 %d",packet->stream_index)
avcodec_decode_video2(ffmpegVideo->codec, frame, &frameCount, packet);
if(!frameCount){
continue;
}
//转换为rgb格式
sws_scale(ffmpegVideo->swsContext,(const uint8_t *const *)frame->data,frame->linesize,0,
frame->height,rgb_frame->data,
rgb_frame->linesize);
LOGE("frame 宽%d,高%d",frame->width,frame->height);
LOGE("rgb格式 宽%d,高%d",rgb_frame->width,rgb_frame->height);
if((pts=av_frame_get_best_effort_timestamp(frame))==AV_NOPTS_VALUE){
pts=0;
}
play = pts*av_q2d(ffmpegVideo->time_base);
//纠正时间
play = ffmpegVideo->synchronize(frame,play);
delay = play - last_play;
if (delay <= 0 || delay > 1) {
delay = last_delay;
}
audio_clock = ffmpegVideo->ffmpegMusic->clock;
last_delay = delay;
last_play = play;
//音频与视频的时间差
diff = ffmpegVideo->clock - audio_clock;
// 在合理范围外 才会延迟 加快
sync_threshold = (delay > 0.01 ? 0.01 : delay);
if (fabs(diff) < 10) {
if (diff <= -sync_threshold) {
delay = 0;
} else if (diff >=sync_threshold) {
delay = 2 * delay;
}
}
start_time += delay;
actual_delay=start_time-av_gettime()/1000000.0;
if (actual_delay < 0.01) {
actual_delay = 0.01;
}
av_usleep(actual_delay*1000000.0+6000);
LOGE("播放视频")
video_call(rgb_frame);
// av_packet_unref(packet);
// av_frame_unref(rgb_frame);
// av_frame_unref(frame);
}
LOGE("free packet");
av_free(packet);
LOGE("free packet ok");
LOGE("free packet");
av_frame_free(&frame);
av_frame_free(&rgb_frame);
sws_freeContext(ffmpegVideo->swsContext);
size_t size = ffmpegVideo->queue.size();
for (int i = 0; i < size; ++i) {
AVPacket *pkt = ffmpegVideo->queue.front();
av_free(pkt);
ffmpegVideo->queue.pop();
}
LOGE("VIDEO EXIT");
pthread_exit(0);
}
从av_usleep(actual_delay*1000000.0+6000);这段代码中可以看出,我们真正需要的就是一个真正需要延迟时间,所以相比于单独播放视频,就是计算出真正需要延迟的时间。
小结
因为有单独的音视频播放作为基础,所以实现同步播放也不是很难,搞清楚以下几点就行:
1.同步播放并没有完美的同步播放,保持在一个合理的范围即可;
2.因为人对声音比较敏感,所以同步播放以音频播放为基础,视频播放以追赶或者延迟形式进行播放;
3.在音频播放时计算出从第一帧开始的播放时间并矫正,用于视频播放的延迟时间的计算;
4.计算出视频播放真正需要延迟的时间,视频播放。
c# 用ffmpeg从视频中截图
概述
之前做了个项目,涉及到上传视频,那么我们知道,一般在界面显示的时候,是需要对视频有个预览的效果,就是显示某一帧视频的画面。
找了下发现http://ffmpeg.org/ 可以实现这个功能。
FFmpeg是一个自由软件,可以运行音频和视频多种格式的录影、转换、流功能,包含了libavcodec——这是一个用于多个项目中音频和视频的解码器库,以及libavformat——一个音频与视频格式转换库。
主要参数
- -i——设置输入档名。
- -f——设置输出格式。
- -y——若输出文件已存在时则覆盖文件。
- -fs——超过指定的文件大小时则结束转换。
- -t——指定输出文件的持续时间,以秒为单位。
- -ss——从指定时间开始转换,以秒为单位。
- -t从-ss时间开始转换(如-ss 00:00:01.00 -t 00:00:10.00即从00:00:01.00开始到00:00:11.00)。
- -title——设置标题。
- -timestamp——设置时间戳。
- -vsync——增减Frame使影音同步。
- -c——指定输出文件的编码。
- -metadata——更改输出文件的元数据。
- -help——查看帮助信息。
ffmpeg的官网地址是:https://www.ffmpeg.org/
ffmpeg的Github项目地址是:https://github.com/FFmpeg/FFmpeg
下面我们来看下ffmpeg如何实现视频截图的操作。
实现方式
1、下载 ffmpeg http://ffmpeg.org/ ,解压后在 bin 目录下找到 ffmpeg.exe
2、命令方式
-i 视频地址 -ss 第几帧 -f image2 图片存放地址
用cmd试一下,首先切换到ffmpeg.exe所在目录,输入命令,回车
3、c#代码实现
using (System.Diagnostics.Process process = new System.Diagnostics.Process()){ process.StartInfo.FileName = @"D:\ffmpeg.exe"; process.StartInfo.Arguments= @"-i D:\111.mp4 -ss 10 -f image2 D:\test\1.jpg"; process.Start();}
或者如下
using System.Diagnostics; ..... ProcessStartInfo startInfo = new ProcessStartInfo("ffmpeg物理路径"); startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.Arguments = " -i 视频文件路径 -y -f image2 -ss 3 -t 0.001 -s 480*360 截图物理路径";//480*360是图片分辨率 startInfo.UseShellExecute = false; try { Process.Start(startInfo); return true; } catch { }
以上就是c# 用ffmpeg从视频中截图的详细内容,更多关于c# 从视频中截图的资料请关注其它相关文章!
- c# 如何对网络信息进行相关设置(ip,dns,网关等)
- C# 文件安全管理需要注意的
- c# 如何自己实现一个ORM框架
- c# 如何实现自动更新程序
- c# 使用WebRequest实现多文件上传
- c# WPF中的TreeView使用详解
- C# Winform 实现TCP发消息
- c# wpf如何使用Blend工具绘制Control样式
- c# 圆形识别方案和直线识别方案的参考示例
- C# 如何实现Token
- c# 在windows中操作IIS设置FTP服务器的示例
- 使用 BenchmarkDotNet 对 C# 代码进行基准测试
今天关于使用ffmpeg从图像创建视频文件和ffmpeg图片生成视频的讲解已经结束,谢谢您的阅读,如果想了解更多关于Android使用FFmpeg(一)--编译ffmpeg、Android使用FFmpeg(三)--ffmpeg实现视频播放、Android使用FFmpeg(六)--ffmpeg实现音视频同步播放、c# 用ffmpeg从视频中截图的相关知识,请在本站搜索。
本文标签: