视频格式转换主要分为两种类型:转封装和转码
音视频封装指的是将编码后的数据放入具有一定规则的容器文件中,比如MP4文件,MOV文件,MP3文件等等。容器文件和编码方法是两个不同的概念,容器文件中可以支持多种编码方式,一种编码方式可以存放在不同的容器文件中,在转封装前需要查看对应的容器文件是否支持当前的编码方式

其大致的流程就是将输入文件解码和输出文件编码,只不过因为不涉及数据格式转换的操作,只需要执行到 AVPacket 层面即可,执行转换所需的时间短
也意味着我们不需要获取编解码器,解码器:AVPacket -> AVFrame 编码器:AVFrame -> AVPacket
extern"C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#include <iostream>
//本次案例中实现 MP4 -> FLV,视频流编码为h.264 音频流编码为aac,两种容器文件都支持这两种编码
void exchangeMuxing() {
AVFormatContext* srcFmtCtx = NULL, * dstFmtCtx = NULL;
AVPacket* pkt = av_packet_alloc();
const char* srcFile = "target.mp4";
const char* dstFile = "result.ts";
int ret;
int* streamMap=NULL;
int streamMapSize;
do {
//解码流程[01]:打开多媒体文件
ret = avformat_open_input(&srcFmtCtx, srcFile, NULL, NULL);
if (ret < 0) {
cout << "Could not open input file" << endl;
break;
}
//解码流程[02]:获取文件中的流信息
ret = avformat_find_stream_info(srcFmtCtx, NULL);
if (ret < 0) {
cout << "Failed find stream information" << endl;
break;
}
av_dump_format(srcFmtCtx, 0, srcFile, 0);
//记录流的数量,并为streamMap分配内存
streamMapSize = srcFmtCtx->nb_streams;
streamMap = (int*)av_malloc_array(streamMapSize, sizeof(*streamMap));
if (streamMap == NULL) {
cout << "Failed malloc array" << endl;
break;
}
//编码流程【01】:打开输出文件
ret = avformat_alloc_output_context2(&dstFmtCtx, NULL, NULL, dstFile);
if (ret < 0) {
cout << "Could not create output context" << endl;
break;
}
ret = avio_open(&dstFmtCtx->pb, dstFile, AVIO_FLAG_READ_WRITE);
if (ret < 0) {
cout << "Could not open output file" << endl;
break;
}
int index = 0;
bool isError = false;
for (int i = 0; i < streamMapSize; i++) {
AVStream* outStream = NULL;
AVStream* inStream = srcFmtCtx->streams[i];
//创建视频流,并复制参数
outStream = avformat_new_stream(dstFmtCtx, NULL);
if (outStream == NULL) {
cout << "Failed allocating output stream" << endl;
isError = true;
break;
}
ret = avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);
if (ret < 0) {
cout << "Failed to copy codec parameters" << endl;
isError = true;
break;
}
//记录对应Stream的索引,[inStream->index : outStream->index]
streamMap[i] = outStream->index;
}
if (isError) break;
//编码流程【02】:向文件中写入头信息
ret = avformat_write_header(dstFmtCtx,NULL);
if (ret<0) {
cout << "Failed to write file heade" << endl;
break;
}
//解码流程[03]:获取AVPacket数据
while (av_read_frame(srcFmtCtx, pkt)>=0) {
AVStream* outStream = NULL,* inStream = NULL;
inStream = srcFmtCtx->streams[pkt->stream_index];
if (pkt->stream_index>=streamMapSize) {
av_packet_unref(pkt);
continue;
}
//查找到对应的输出流的索引并赋值
pkt->stream_index = streamMap[pkt->stream_index];
outStream = dstFmtCtx->streams[pkt->stream_index];
//转换pkt的时间戳,使得与输出流的时间基相匹配
pkt->pts = av_rescale_q_rnd(pkt->pts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt->dts = av_rescale_q_rnd(pkt->dts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt->duration = av_rescale_q(pkt->duration, inStream->time_base, outStream->time_base);
pkt->pos = -1;
//编码流程【03】:将AVPacket数据写入文件中
ret=av_interleaved_write_frame(dstFmtCtx,pkt);
if (ret<0) {
cout<< "Failed to write packet" <<endl;
break;
}
av_packet_unref(pkt);
}
//编码流程【04】:向文件中写入文件尾部标识,并释放该文件
av_write_trailer(dstFmtCtx);
} while (0);
//释放资源
av_packet_free(&pkt);
if (srcFmtCtx) avformat_close_input(&srcFmtCtx);
if (dstFmtCtx) {
avformat_free_context(dstFmtCtx);
avio_closep(&dstFmtCtx->pb);
}
if (streamMap) av_free(streamMap);
}
av_q2d
av_q2d就是将时间基转换为对应的 double 值
static inline double av_q2d(AVRational a){
return a.num / (double) a.den;
}
av_rescale_q_rnd与av_rescale_q
av_rescale_q_rnd函数进行 a*bq/cq的运算,然后将计算结果进行四舍五入
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq){
return av_rescale_q_rnd(a, bq, cq, AV_ROUND_NEAR_INF);
}
int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq, enum AVRounding rnd){
int64_t b = bq.num * (int64_t)cq.den;
int64_t c = cq.num * (int64_t)bq.den;
return av_rescale_rnd(a, b, c, rnd);
}
其中enum AVRounding rnd指定四舍五入的方式,共有以下6种类型:
enum AVRounding {
AV_ROUND_ZERO = 0, //计算结果靠近0 -3/2 → -1
AV_ROUND_INF = 1, //计算结果远离0 -3/2 → -2
AV_ROUND_DOWN = 2, //计算结果趋于负无穷,向下取整 9/5 → 1
AV_ROUND_UP = 3, //计算结果趋于正无穷,向上取整 6/5 → 2
AV_ROUND_NEAR_INF = 5, //计算结果四舍五入,小于0.5取值趋向0,大于0.5取值趋远于0
/**
* AV_ROUND_PASS_MINMAX用于避免出现 AV_NOPTS_VALUE 参与运算的情况
* 它需要通过位运算符'|'与其他枚举值一起使用,该值是一个位掩码
*
* @代码示例
* 正常:
* av_rescale_rnd(3, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
* // Rescaling 3:
* // Calculating 3 * 1 / 2
* // 3 / 2 is rounded up to 2
* // => 2
*
* AV_NOPTS_VALUE参与运算:
* av_rescale_rnd(AV_NOPTS_VALUE, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
* // Rescaling AV_NOPTS_VALUE:
* // AV_NOPTS_VALUE == INT64_MIN
* // AV_NOPTS_VALUE is passed through
* // => AV_NOPTS_VALUE
* @endcode
*/
AV_ROUND_PASS_MINMAX = 8192,
};
源容器格式的音/视频编码方式在目标容器不被支持,也就无法使用转封装方式进行转换,此时就需要先解码再编码实现视频格式转换,即转码
如果需要改变数据内容,如音视频码率、视频分辨率等,那么也需要使用转码的方式,其实 转码 = 解码 + 编码,不过要注意编码格式是否为有损压缩,可能会导致视频画面模糊

因为代码量较多且逻辑较为复杂,这里选择通过类的方式实现并划分为各个模块进行讲解:基础模块、视频模块、音频模块
extern"C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
}
#include <iostream>
using namespace std;
class TranscodeHandler{
public:
//执行具体的转码操作
void doTranscode();
private:
//实现文件从 flv -> mp4
const char* srcFile = "target.flv";
const char* dstFile = "result.mp4";
//声明相关变量,基本上都是成对出现:输入与输出
AVFormatContext* srcFmtCtx = NULL, * dstFmtCtx = NULL;
AVCodecContext* srcVideoCodecCtx = NULL, * srcAudioCodecCtx = NULL;
AVCodecContext* dstVideoCodecCtx = NULL, * dstAudioCodecCtx = NULL;
AVStream* srcVideoStream = NULL, * srcAudioStream = NULL;
AVStream* dstVideoStream = NULL, * dstAudioStream = NULL;
AVFrame* srcVideoFrame = NULL, * srcAudioFrame = NULL;
AVFrame* dstVideoFrame = NULL, * dstAudioFrame = NULL;
SwsContext* videoSwsCtx = NULL;
SwrContext* audioSwrCtx = NULL;
AVPacket* pkt = av_packet_alloc();
//pts
int videoPts = 0;
int audioPts = 0;
//释放资源
void releaseRes();
//添加流,并设置参数
int addVideoStream();
int addAudioStream();
//编解码视频流
int decodeVideo(AVPacket *pkt);
int encodeVideo(AVFrame* frame);
//编解码音频流
int decodeAudio(AVPacket* pkt);
int encodeAudio(AVFrame* frame);
};
与组合视频流与音频流那篇文章中的代码实现流程一致,只是从读取源数据变为了从源媒体文件中读取数据;参数的设置从手动设置全部必要参数变为了拷贝参数+手动修改部分参数
//这里与组合视频流与音频流的代码很相似
void TranscodeHandler::doTranscode() {
int ret;
do {
//打开本地文件
ret = avformat_open_input(&srcFmtCtx, srcFile, NULL, NULL);
if (ret < 0) {
cout << "Could not open input" << endl;
break;
}
//获取多媒体文件信息
ret = avformat_find_stream_info(srcFmtCtx, NULL);
if (ret < 0) {
cout << "Could not find stream info" << endl;
break;
}
av_dump_format(srcFmtCtx, 0, srcFile, 0);
//创建输出结构上下文 AVFormatContext
ret = avformat_alloc_output_context2(&dstFmtCtx, NULL, NULL, dstFile);
if (ret < 0) {
cout << "Could not create output context" << endl;
break;
}
//打开文件
ret = avio_open(&dstFmtCtx->pb, dstFile, AVIO_FLAG_READ_WRITE);
if (ret < 0) {
cout << "Could not open output file" << endl;
break;
}
//添加一个视频流
ret = addVideoStream();
if (ret < 0) {
cout << "Add video stream fail" << endl;
break;
}
//添加一个音频流
ret = addAudioStream();
if (ret < 0) {
cout << "Add audio stream fail" << endl;
break;
}
//写入文件头信息
ret=avformat_write_header(dstFmtCtx,NULL);
if (ret != AVSTREAM_INIT_IN_WRITE_HEADER) {
cout << "Write file header fail" << endl;
break;
}
av_dump_format(dstFmtCtx,0,dstFile,1);
//申请视频流使用的Frame
srcVideoFrame = av_frame_alloc();
dstVideoFrame = av_frame_alloc();
//为av_frame_get_buffer设置必要的参数
dstVideoFrame->width = dstVideoCodecCtx->width;
dstVideoFrame->height = dstVideoCodecCtx->height;
dstVideoFrame->format = dstVideoCodecCtx->pix_fmt;
//获取Frame的数据缓冲区
ret = av_frame_get_buffer(dstVideoFrame, 0);
if (ret < 0) {
cout << "video av_frame_get_buffer fail" << endl;
break;
}
//判断是否需要进行转换
if (srcVideoCodecCtx->width != dstVideoCodecCtx->width ||
srcVideoCodecCtx->height != dstVideoCodecCtx->height ||
srcVideoCodecCtx->pix_fmt != dstVideoCodecCtx->pix_fmt) {
videoSwsCtx = sws_getContext(
srcVideoCodecCtx->width, srcVideoCodecCtx->height, srcVideoCodecCtx->pix_fmt,
dstVideoCodecCtx->width, dstVideoCodecCtx->height, dstVideoCodecCtx->pix_fmt,
SWS_BILINEAR,NULL,NULL,NULL
);
if (videoSwsCtx==NULL) {
cout << "Get SwsContext fail" << endl;
break;
}
}
//申请音频流使用的Frame
srcAudioFrame = av_frame_alloc();
dstAudioFrame = av_frame_alloc();
//用于判断是否需要进行转换的参数
//为av_frame_get_buffer设置必要的参数,nb_samples、channel_layout、format
dstAudioFrame->nb_samples = dstAudioCodecCtx->frame_size;
dstAudioFrame->channel_layout = dstAudioCodecCtx->channel_layout;
dstAudioFrame->format = dstAudioCodecCtx->sample_fmt;
dstAudioFrame->channels = dstAudioCodecCtx->channels;
dstAudioFrame->sample_rate = dstAudioCodecCtx->sample_rate;
//获取Frame的数据缓冲区
ret = av_frame_get_buffer(dstAudioFrame, 0);
if (ret < 0) {
cout << "audio av_frame_get_buffer fail" << endl;
break;
}
//判断是否需要进行转换
if (dstAudioCodecCtx->sample_fmt != srcAudioCodecCtx->sample_fmt ||
dstAudioCodecCtx->channel_layout != srcAudioCodecCtx->channel_layout||
dstAudioCodecCtx->sample_rate != srcAudioCodecCtx->sample_rate||
dstAudioCodecCtx->frame_size != srcAudioCodecCtx->frame_size||
dstAudioCodecCtx->channels != srcAudioCodecCtx->channels) {
audioSwrCtx = swr_alloc_set_opts(NULL,
dstAudioCodecCtx->channel_layout,dstAudioCodecCtx->sample_fmt,dstAudioCodecCtx->sample_rate,
srcAudioCodecCtx->channel_layout,srcAudioCodecCtx->sample_fmt,srcAudioCodecCtx->sample_rate,
0,NULL);
if (audioSwrCtx == NULL) {
cout << "Get SwrContext fail" << endl;
break;
}
}
//从源文件中读取数据
while (av_read_frame(srcFmtCtx,pkt)>=0) {
//判断属于哪个流
if (pkt->stream_index==srcVideoStream->index) {
//解码视频流
decodeVideo(pkt);
}else if (pkt->stream_index == srcAudioStream->index) {
//解码视频流
decodeAudio(pkt);
}
}
//刷新缓冲(视频)
decodeVideo(NULL);
encodeVideo(NULL);
//刷新缓冲(音频)
decodeAudio(NULL);
if (audioSwrCtx) {
while (swr_convert_frame(audioSwrCtx, dstAudioFrame, NULL) >= 0) {
if (dstAudioFrame->nb_samples == 0) break;
dstAudioFrame->pts = audioPts;
audioPts += dstAudioFrame->nb_samples;
encodeAudio(dstAudioFrame);
}
}
encodeAudio(NULL);
//向文件中写入文件尾部标识,并释放该文件
av_write_trailer(dstFmtCtx);
} while (0);
//释放资源
releaseRes();
}
//释放相关的资源
void TranscodeHandler::releaseRes() {
if (srcFmtCtx) avformat_close_input(&srcFmtCtx);
if (dstFmtCtx) {
avformat_free_context(dstFmtCtx);
avio_close(dstFmtCtx->pb);
dstFmtCtx = NULL;
}
if (srcVideoCodecCtx) avcodec_free_context(&srcVideoCodecCtx);
if (srcAudioCodecCtx) avcodec_free_context(&srcAudioCodecCtx);
if (dstVideoCodecCtx) avcodec_free_context(&dstVideoCodecCtx);
if (dstAudioCodecCtx) avcodec_free_context(&dstAudioCodecCtx);
if (srcVideoFrame) av_frame_free(&srcVideoFrame);
if (srcAudioFrame) av_frame_free(&srcAudioFrame);
if (dstVideoFrame) av_frame_free(&dstVideoFrame);
if (dstAudioFrame) av_frame_free(&dstAudioFrame);
if (pkt)av_packet_free(&pkt);
if (videoSwsCtx) sws_freeContext(videoSwsCtx);
if (audioSwrCtx) swr_free(&audioSwrCtx);
}
视频模块中又分为 初始化 、解码 、编码三个部分
初始化模块中主要工作为给输出文件添加视频流,并为之后的视频流解码与编码设置参数
//返回0表示成功,返回一个负数表示失败
int TranscodeHandler::addVideoStream() {
int ret = 0;
AVCodec* srcCodec = NULL, * dstCodec = NULL;
do {
//查找视频流,并根据视频流格式给srcCodec解码器赋值
ret = av_find_best_stream(srcFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &srcCodec, 0);
if (ret < 0) {
cout << "Could not find video stream" << endl;
break;
}
//ret返回的就是找到的视频流的索引
srcVideoStream = srcFmtCtx->streams[ret];
AVCodecParameters* srcParam = srcVideoStream->codecpar;
//申请编码器上下文结构体
srcVideoCodecCtx = avcodec_alloc_context3(srcCodec);
if (srcVideoCodecCtx == NULL) {
ret = -1;
cout << "Could not alloc video context" << endl;
break;
}
//将参数传给解码器上下文
ret = avcodec_parameters_to_context(srcVideoCodecCtx, srcParam);
if (ret < 0) {
cout << "Fail copy video parameters to context" << endl;
break;
}
//打开解码器
ret = avcodec_open2(srcVideoCodecCtx, srcCodec, NULL);
if (ret < 0) {
printf("cannot open video decoder\n");
break;
}
//--------------------------------------------------
//查找编码器
dstCodec = avcodec_find_encoder(dstFmtCtx->oformat->video_codec);
if (dstCodec == NULL) {
ret = -1;
cout << "Cannot find any video endcoder" << endl;
break;
}
//申请编码器上下文结构体
dstVideoCodecCtx = avcodec_alloc_context3(dstCodec);
if (dstVideoCodecCtx == NULL) {
ret = -1;
cout << "Cannot alloc video dst context" << endl;
break;
}
//创建视频流
dstVideoStream = avformat_new_stream(dstFmtCtx, dstCodec);
if (dstVideoStream == NULL) {
ret = -1;
cout << "Could not new video stream" << endl;
break;
}
AVCodecParameters* dstParam = dstVideoStream->codecpar;
//拷贝输入文件中视频流的参数
ret = avcodec_parameters_copy(dstParam, srcParam);
if (ret < 0) {
cout << "Fail copy video parameters" << endl;
break;
}
//可以在这里更改需要修改的参数
dstParam->codec_id = dstCodec->id;
dstParam->codec_tag = 0;
//将参数传给解码器上下文
ret = avcodec_parameters_to_context(dstVideoCodecCtx, dstParam);
if (ret < 0) {
cout << "Fail copy video parameters to dst context" << endl;
break;
}
dstVideoCodecCtx->time_base = AVRational{ 1, 25 };
if (dstVideoCodecCtx->codec_id == AV_CODEC_ID_H264) {
av_opt_set(dstVideoCodecCtx->priv_data, "preset", "slow", 0);
}
if (dstFmtCtx->oformat->flags & AVFMT_GLOBALHEADER) {
dstVideoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
dstVideoCodecCtx->flags |= AV_CODEC_FLAG2_LOCAL_HEADER;
}
//打开解码器
ret = avcodec_open2(dstVideoCodecCtx, dstCodec, NULL);
if (ret < 0) {
printf("cannot open video encoder\n");
break;
}
//再将dstVideoCodecCtx设置的参数传给dstParam,用于写入头文件信息
ret = avcodec_parameters_from_context(dstParam, dstVideoCodecCtx);
if (ret < 0) {
cout << "Fail copy video parameters from dst context" << endl;
break;
}
} while (0);
return ret;
}
/*
* 解码输入文件
* @param pkt:包含数据的Packet,传入NULL表示刷新缓存
* @return 返回0表示成功,返回一个负数表示失败
*/
int TranscodeHandler::decodeVideo(AVPacket* pkt) {
int ret = 0;
//发送包数据去进行解析获得帧数据
if (avcodec_send_packet(srcVideoCodecCtx,pkt)>=0) {
//获取解码器解析后产生的Frame
while (avcodec_receive_frame(srcVideoCodecCtx,srcVideoFrame)>=0) {
//是否需要进行转换操作
if (videoSwsCtx) {
ret=sws_scale(videoSwsCtx, srcVideoFrame->data, srcVideoFrame->linesize, 0, srcVideoCodecCtx->height, dstVideoFrame->data, dstVideoFrame->linesize);
if (ret < 0) {
cout << "Cannot scale frame" << endl;
break;
}
}else {
//不需要转换直接拷贝Frame中的数据即可
ret = av_frame_copy(dstVideoFrame, srcVideoFrame);
if (ret < 0) {
cout << "Not copy video frame" << endl;
break;
}
}
dstVideoFrame->pts = videoPts++;
//执行编码操作
ret = encodeVideo(dstVideoFrame);
if (ret < 0) {
cout<<"Do encodeVideo fail"<<endl;
break;
}
}
av_packet_unref(pkt);
}
return ret;
}
注:不可以直接将输入文件解析出的 Frame 传给 encodeVideo 进行编码,因为 Frame 不仅仅包含数据,还包含了其他用于播放时的参数
直接使用 srcVideoFrame 导致出现警告 warning, too many B-frames in a row,最终文件没有视频画面
/*
* 编码输出文件
* @param frame:包含数据的Frame,传入NULL表示刷新缓存
* @return 返回0表示成功,返回一个负数表示失败
*/
int TranscodeHandler::encodeVideo(AVFrame* frame) {
int ret=0;
//将frame发送至编码器进行编码,codecCtx中保存了codec
//当frame为NULL时,表示将缓冲区中的数据读取出来
if (avcodec_send_frame(dstVideoCodecCtx, frame) >= 0) {
//接收编码后形成的packet
//查看源码后,会发现该方法会先调用 av_packet_unref,在执行接收操作
while (avcodec_receive_packet(dstVideoCodecCtx,pkt)>=0) {
//设置对应的流索引
pkt->stream_index = dstVideoStream->index;
pkt->pos = -1;
//转换pts至基于时间基的pts,可以理解为视频帧显示的时间戳
av_packet_rescale_ts(pkt, dstVideoCodecCtx->time_base, dstVideoStream->time_base);
cout << "encoder success:" << pkt->size << endl;
//将包数据写入文件中,该方法不用使用 av_packet_unref
ret = av_interleaved_write_frame(dstFmtCtx, pkt);
if (ret < 0) {
char errStr[256];
av_strerror(ret, errStr, 256);
cout << "error is:" << errStr << endl;
break;
}
}
}
return ret;
}
音频模块与视频模块一样,分为 初始化 、解码 、编码三个部分
初始化模块中主要工作为给输出文件添加音频流,并为之后的音频流解码与编码设置参数
//返回0表示成功,返回一个负数表示失败
int TranscodeHandler::addAudioStream(){
int ret;
AVCodec* srcCodec = NULL, * dstCodec = NULL;
do {
//查找音频流,并根据音频流格式给srcCodec解码器赋值
ret = av_find_best_stream(srcFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, &srcCodec, 0);
if (ret < 0) {
cout << "Could not find audio stream" << endl;
break;
}
//ret返回的就是找到的视频流的索引
srcAudioStream = srcFmtCtx->streams[ret];
AVCodecParameters* srcParam = srcAudioStream->codecpar;
//申请编码器上下文结构体
srcAudioCodecCtx = avcodec_alloc_context3(srcCodec);
if (srcAudioCodecCtx == NULL) {
ret = -1;
cout << "Could not alloc audio context" << endl;
break;
}
//将参数传给解码器上下文
ret = avcodec_parameters_to_context(srcAudioCodecCtx, srcParam);
if (ret < 0) {
cout << "Fail copy audio parameters to context" << endl;
break;
}
//打开解码器
ret = avcodec_open2(srcAudioCodecCtx, srcCodec, NULL);
if (ret < 0) {
printf("cannot open audio decoder\n");
break;
}
//--------------------------------------------------
//查找编码器
dstCodec = avcodec_find_encoder(dstFmtCtx->oformat->audio_codec);
if (dstCodec == NULL) {
ret = -1;
cout << "Cannot find any audio endcoder" << endl;
break;
}
//申请编码器上下文结构体
dstAudioCodecCtx = avcodec_alloc_context3(dstCodec);
if (dstAudioCodecCtx == NULL) {
ret = -1;
cout << "Cannot alloc audio dst context" << endl;
break;
}
//创建视频流
dstAudioStream = avformat_new_stream(dstFmtCtx, dstCodec);
if (dstAudioStream == NULL) {
ret = -1;
cout << "Could not new audio stream" << endl;
break;
}
AVCodecParameters* dstParam = dstAudioStream->codecpar;
//拷贝输入文件中视频流的参数
ret = avcodec_parameters_copy(dstParam, srcParam);
if (ret < 0) {
cout << "Fail copy audio parameters" << endl;
break;
}
//可以在这里更改需要修改的参数
dstParam->codec_id = dstCodec->id;
dstParam->codec_tag = 0;
//将参数传给解码器上下文
ret = avcodec_parameters_to_context(dstAudioCodecCtx, dstParam);
if (ret < 0) {
cout << "Fail copy video parameters to dst context" << endl;
break;
}
//打开解码器
ret = avcodec_open2(dstAudioCodecCtx, dstCodec, NULL);
if (ret < 0) {
printf("cannot open audio encoder\n");
break;
}
//再将dstVideoCodecCtx设置的参数传给dstParam,用于写入头文件信息
ret = avcodec_parameters_from_context(dstParam, dstAudioCodecCtx);
if (ret < 0) {
cout << "Fail copy video parameters from dst context" << endl;
break;
}
} while (0);
return ret;
}
/*
* 解码输入文件
* @param pkt:包含数据的Packet,传入NULL表示刷新缓存
* @return 返回0表示成功,返回一个负数表示失败
*/
int TranscodeHandler::decodeAudio(AVPacket* pkt) {
int ret = 0;
//发送包数据去进行解析获得帧数据
if (avcodec_send_packet(srcAudioCodecCtx,pkt)>=0) {
//获取解码器解析后产生的Frame
while (avcodec_receive_frame(srcAudioCodecCtx,srcAudioFrame)>=0) {
//是否需要进行转换操作
if (audioSwrCtx) {
ret = swr_convert_frame(audioSwrCtx, dstAudioFrame, srcAudioFrame);
if (ret<0) {
cout << "Cannot convert frame" << endl;
break;
}
}else {
//不需要转换直接拷贝Frame中的数据即可
ret = av_frame_copy(dstAudioFrame,srcAudioFrame);
if (ret<0) {
cout << "Not copy audio frame" << endl;
break;
}
}
dstAudioFrame->pts = audioPts;
audioPts += dstAudioFrame->nb_samples;
//执行编码操作
ret=encodeAudio(dstAudioFrame);
if (ret < 0) {
cout << "Do encodeAudio fail" << endl;
break;
}
}
av_packet_unref(pkt);
}
return ret;
}
/*
* 编码输出文件
* @param frame:包含数据的Frame,传入NULL表示刷新缓存
* @return 返回0表示成功,返回一个负数表示失败
*/
int TranscodeHandler::encodeAudio(AVFrame* frame){
int ret = 0;
//将frame发送至编码器进行编码,codecCtx中保存了codec
//当frame为NULL时,表示将缓冲区中的数据读取出来
if (avcodec_send_frame(dstAudioCodecCtx,frame)>=0) {
//接收编码后形成的packet
//查看源码后,会发现该方法会先调用 av_packet_unref,在执行接收操作
while (avcodec_receive_packet(dstAudioCodecCtx,pkt)>=0) {
//设置对应的流索引
pkt->stream_index = dstAudioStream->index;
pkt->pos = -1;
//转换pts至基于时间基的pts
av_packet_rescale_ts(pkt,dstAudioCodecCtx->time_base,dstAudioStream->time_base);
cout << "encoder audio success pts:" << pkt->pts << endl;
//将包数据写入文件中,该方法不用使用 av_packet_unref
ret = av_interleaved_write_frame(dstFmtCtx, pkt);
if (ret < 0) {
char errStr[256];
av_strerror(ret, errStr, 256);
cout << "error is:" << errStr << endl;
break;
}
}
}
return ret;
}
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]
这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[
Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/
我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa
这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build
我正在使用Rails构建一个简单的聊天应用程序。当用户输入url时,我希望将其输出为html链接(即“url”)。我想知道在Ruby中是否有任何库或众所周知的方法可以做到这一点。如果没有,我有一些不错的正则表达式示例代码可以使用... 最佳答案 查看auto_linkRails提供的辅助方法。这会将所有URL和电子邮件地址变成可点击的链接(htmlanchor标记)。这是文档中的代码示例。auto_link("Gotohttp://www.rubyonrails.organdsayhellotodavid@loudthinking.
我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最