日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java和ffmpeg使用内存转码_FFMPEG基于内存的转码实例

發(fā)布時間:2025/3/20 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java和ffmpeg使用内存转码_FFMPEG基于内存的转码实例 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

我在6月份寫了篇文章《FFMPEG基于內存的轉碼實例》,講如何把視頻轉碼后放到內存,然后通過網絡發(fā)送出去。但該文章只完成了一半,即輸入的數據依然是從磁盤文件中讀取。在實際應用中,有很多數據是放到內存的,比如播放從服務器接收到的視頻,就是在內存中的。時隔2個月,項目終于完成了,雖然在收尾階段會花費大量時間,但也算空閑了點。于是就繼續(xù)完善。

本文中,假定的使用場合是,有一個已經放到內存的視頻,需要將它轉碼成另一種封裝格式,還是放到內存中。由于是測試,首先將視頻從文件中讀取到內存,最后會將轉換好的視頻寫入到另一個文件以檢查是否正常。當然,限于能力,代碼不可能適合于所有情況,但卻可以清楚演示出自定義的IO輸入輸出的用法。

技術要點簡述如下:

1、用戶自定義的操作

對于內存的操作使用結構體封裝:

typedef struct AVIOBufferContext {

unsigned char* ptr;

int pos;

int totalSize;

int realSize;

}AVIOBufferContext;

輸入、輸出均使用該結構體:

AVIOBufferContext g_avbuffer_in;

AVIOBufferContext g_avbuffer_out;

實現(xiàn),read、write、seek函數。其中read為讀取時使用到的,其它2個是寫入內存要使用的。以read為例:

static int my_read(void *opaque, unsigned char *buf, int size)

{

AVIOBufferContext* op = (AVIOBufferContext*)opaque;

int len = size;

if (op->pos + size > op->totalSize)

{

len = op->totalSize - op->pos;

}

memcpy(buf, op->ptr + op->pos, len);

if (op->pos + len >= op->realSize)

op->realSize += len;

op->pos += len;

return len;

}

實質進行的是讀取已有內存的size數據,拷貝到buf中。opaque方便參數傳遞。注意,在拷貝時要對pos累加。

其它函數類似。

2、輸出配置關鍵代碼:

avio_out =avio_alloc_context((unsigned char *)g_ptr_out, IO_BUFFER_SIZE, 1,

&g_avbuffer_out, NULL, my_write, my_seek);

avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);

if (!ofmt_ctx)

{

printf( "Could not create output context\n");

ret = AVERROR_UNKNOWN;

goto end;

}

ofmt_ctx->pb=avio_out; // 賦值自定義的IO結構體

ofmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定為自定義

這個跟上述提到的文章是一致的。只是多了個自定義的結構體。

3、輸入配置關鍵代碼:

avio_in =avio_alloc_context((unsigned char *)g_ptr_in, IO_BUFFER_SIZE, 0,

&g_avbuffer_in, my_read, NULL, NULL);

if (!avio_in)

{

printf( "avio_alloc_context for input failed\n");

ret = AVERROR_UNKNOWN;

goto end;

}

// 分配輸入的AVFormatContext

ifmt_ctx=avformat_alloc_context();

if (!ifmt_ctx)

{

printf( "Could not create output context\n");

ret = AVERROR_UNKNOWN;

goto end;

}

ifmt_ctx->pb=avio_in; // 賦值自定義的IO結構體

ifmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定為自定義

if ((ret = avformat_open_input(&ifmt_ctx, "wtf", NULL, NULL)) < 0)

{

printf("Cannot open input file\n");

return ret;

}

if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)

{

printf("Cannot find stream information\n");

return ret;

}

對于avio_alloc_context的賦值和輸出一樣,只是沒有了write和seek。對于輸入所用的AVFormatContext變量,用avformat_alloc_context來分配。由于是讀內存的數據,因此avformat_open_input就不用指定文件名了。

我在代碼中盡量加了注釋,下面是代碼:

/**

他山之石,學習為主,版權所無,翻版不究,有錯無責

Late Lee 2015.08

基于內存的格式封裝測試(從內存視頻轉換到另一片內存視頻)

使用

./a.out a.avi a.mkv

支持的:

avi mkv mp4 flv ts ...

參考:

http://blog.csdn.net/leixiaohua1020/article/details/25422685

log

新版本出現(xiàn):

Using AVStream.codec.time_base as a timebase hint to the muxer is

deprecated. Set AVStream.time_base instead.

test passed!!

mp4->avi failed

出現(xiàn):

H.264 bitstream malformed, no startcode found, use the h264_mp4toannexb bitstream filter

解決見:

http://blog.chinaunix.net/uid-11344913-id-4432752.html

官方解釋:

https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb

ts -> avi passed

其它:

1、傳遞給ffmpeg的avio_alloc_context中的內存p和大小size,可以使用32768。

如果轉換后的數據保存在內存p1,這個內存p1一定要和前面所說的p不同。因為

在自定義的write中的buf參數,就是p,所以要拷貝到其它內存。

如定義p為32768,但定義p1為50MB,可以轉換50MB的視頻

測試:

p為32768時,需調用write 1351次

2倍大小時,調用write 679次

p越大,調用次數最少,內存消耗越大

(用time測試,時間上沒什么變化,前者為4.7s,后者為4.6s,但理論上內存大一點好)

2、優(yōu)化:

轉換功能接口封裝為類,把write、seek等和內存有關的操作放到類外部實現(xiàn),

再傳遞到該類中,該類沒有內存管理更好一些。

todo

重復讀文件,如何做?

*/

#include #include #include #include #include #include extern "C" {

#include "libavcodec/avcodec.h"

#include "libavformat/avformat.h"

#include "libswscale/swscale.h"

}

#include "file_utils.h"

#ifndef min

#define min(a,b) ((a) > (b) ? (b) : (a))

#endif

#define _LL_DEBUG_

// low level debug

#ifdef _LL_DEBUG_

#define debug(fmt, ...) printf(fmt, ##__VA_ARGS__)

#define LL_DEBUG(fmt, ...) printf("[DEBUG %s().%d @ %s]: " fmt, __func__, __LINE__, P_SRC, ##__VA_ARGS__)

#else

#define debug(fmt, ...)

#define LL_DEBUG(fmt, ...)

#endif

#define DEFAULT_MEM (10*1024*1024)

//參考file協(xié)議的內存,使用大小32768,大一點也可以

#define IO_BUFFER_SIZE (32768*1)

typedef struct AVIOBufferContext {

unsigned char* ptr;

int pos;

int totalSize;

int realSize;

}AVIOBufferContext;

// note 這兩個是用戶視頻數據,

// g_avbuffer_in為已經讀取的視頻

// g_avbuffer_out是ffmpeg轉換后的視頻,直接將該內存寫入文件即可

AVIOBufferContext g_avbuffer_in;

AVIOBufferContext g_avbuffer_out;

// note這兩個是FFMPEG內部使用的IO內存,與AVIOBufferContext的ptr不同

// 在測試時,發(fā)現(xiàn)直接定義為數組,會有錯誤,故使用malloc

static char *g_ptr_in = NULL;

static char *g_ptr_out = NULL;

// 每次read_frame時,就會調用到這個函數,該函數從g_avbuffer_in讀數據

static int my_read(void *opaque, unsigned char *buf, int size)

{

AVIOBufferContext* op = (AVIOBufferContext*)opaque;

int len = size;

if (op->pos + size > op->totalSize)

{

len = op->totalSize - op->pos;

}

memcpy(buf, op->ptr + op->pos, len);

if (op->pos + len >= op->realSize)

op->realSize += len;

op->pos += len;

return len;

}

static int my_write(void *opaque, unsigned char *buf, int size)

{

AVIOBufferContext* op = (AVIOBufferContext*)opaque;

if (op->pos + size > op->totalSize)

{

// 重新申請

// 根據數值逐步加大

int newTotalLen = op->totalSize*sizeof(char) * 3 / 2;

unsigned char* ptr = (unsigned char*)av_realloc(op->ptr, newTotalLen);

if (ptr == NULL)

{

// todo 是否在此處釋放內存?

return -1;

}

debug("org ptr: %p new ptr: %p size: %d(%0.fMB) ", op->ptr, ptr,

newTotalLen, newTotalLen/1024.0/1024.0);

op->totalSize = newTotalLen;

op->ptr = ptr;

debug(" realloc!!!!!!!!!!!!!!!!!!!!!!!\n");

}

memcpy(op->ptr + op->pos, buf, size);

if (op->pos + size >= op->realSize)

op->realSize += size;

//static int cnt = 1;

//debug("%d write %p %p pos: %d len: %d\n", cnt++, op->ptr, buf, op->pos, size);

op->pos += size;

return 0;

}

static int64_t my_seek(void *opaque, int64_t offset, int whence)

{

AVIOBufferContext* op = (AVIOBufferContext*)opaque;

int64_t new_pos = 0; // 可以為負數

int64_t fake_pos = 0;

switch (whence)

{

case SEEK_SET:

new_pos = offset;

break;

case SEEK_CUR:

new_pos = op->pos + offset;

break;

case SEEK_END: // 此處可能有問題

new_pos = op->totalSize + offset;

break;

default:

return -1;

}

fake_pos = min(new_pos, op->totalSize);

if (fake_pos != op->pos)

{

op->pos = fake_pos;

}

//debug("seek pos: %d(%d)\n", offset, op->pos);

return new_pos;

}

int remuxer_mem_read(int argc, char* argv[])

{

//輸入對應一個AVFormatContext,輸出對應一個AVFormatContext

AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;

AVIOContext *avio_in = NULL, *avio_out = NULL;

const char *in_filename = NULL, *out_filename = NULL;

AVPacket pkt;

int ret = 0;

if (argc < 3)

{

printf("usage: %s [input file] [output file]\n", argv[0]);

printf("eg %s foo.avi bar.ts\n", argv[0]);

return -1;

}

in_filename = argv[1];

out_filename = argv[2];

memset(&g_avbuffer_in, '\0', sizeof(AVIOBufferContext));

memset(&g_avbuffer_out, '\0', sizeof(AVIOBufferContext));

read_file(in_filename, (char**)&g_avbuffer_in.ptr, &g_avbuffer_in.totalSize);

// 分配輸出視頻數據空間

g_avbuffer_out.ptr = (unsigned char*)av_realloc(NULL, DEFAULT_MEM*sizeof(char)); // new

if (g_avbuffer_out.ptr == NULL)

{

debug("alloc output mem failed.\n");

return -1;

}

g_avbuffer_out.totalSize = DEFAULT_MEM;

memset(g_avbuffer_out.ptr, '\0', g_avbuffer_out.totalSize);

g_ptr_in = (char*)malloc(IO_BUFFER_SIZE*sizeof(char));

g_ptr_out = (char*)malloc(IO_BUFFER_SIZE*sizeof(char));

// 初始化

av_register_all();

// 輸出相關

// note 要指定IO內存,還在指定自定義的操作函數,這里有write和seek

avio_out =avio_alloc_context((unsigned char *)g_ptr_out, IO_BUFFER_SIZE, 1,

&g_avbuffer_out, NULL, my_write, my_seek);

if (!avio_out)

{

printf( "avio_alloc_context failed\n");

ret = AVERROR_UNKNOWN;

goto end;

}

// 分配AVFormatContext

// 為方便起見,使用out_filename來根據輸出文件擴展名來判斷格式

// 如果要使用如“avi”、“mp4”等指定,賦值給第3個參數即可

// 注意該函數會分配AVOutputFormat

avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);

if (!ofmt_ctx)

{

printf( "Could not create output context\n");

ret = AVERROR_UNKNOWN;

goto end;

}

ofmt_ctx->pb=avio_out; // 賦值自定義的IO結構體

ofmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定為自定義

debug("guess format: %s(%s) flag: %d\n", ofmt_ctx->oformat->name,

ofmt_ctx->oformat->long_name, ofmt_ctx->oformat->flags);

// 輸入相關

// 分配自定義的AVIOContext 要區(qū)別于輸出的buffer

// 由于數據已經在內存中,所以指定read即可,不用write和seek

avio_in =avio_alloc_context((unsigned char *)g_ptr_in, IO_BUFFER_SIZE, 0,

&g_avbuffer_in, my_read, NULL, NULL);

if (!avio_in)

{

printf( "avio_alloc_context for input failed\n");

ret = AVERROR_UNKNOWN;

goto end;

}

// 分配輸入的AVFormatContext

ifmt_ctx=avformat_alloc_context();

if (!ifmt_ctx)

{

printf( "Could not create output context\n");

ret = AVERROR_UNKNOWN;

goto end;

}

ifmt_ctx->pb=avio_in; // 賦值自定義的IO結構體

ifmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; // 指定為自定義

// 注:第二個參數本來是文件名,但基于內存,不再有意義,隨便用字符串

if ((ret = avformat_open_input(&ifmt_ctx, "wtf", NULL, NULL)) < 0)

{

printf("Cannot open input file\n");

return ret;

}

if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)

{

printf("Cannot find stream information\n");

return ret;

}

// 復制所有的stream

for (int i = 0; i < (int)(ifmt_ctx->nb_streams); i++)

{

//根據輸入流創(chuàng)建輸出流

AVStream *in_stream = ifmt_ctx->streams[i];

AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);

if (!out_stream)

{

printf( "Failed allocating output stream\n");

ret = AVERROR_UNKNOWN;

goto end;

}

//復制AVCodecContext的設置

ret = avcodec_copy_context(out_stream->codec, in_stream->codec);

if (ret < 0)

{

printf( "Failed to copy context from input to output stream codec context\n");

goto end;

}

out_stream->codec->codec_tag = 0;

if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)

out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;

}

//輸出一下格式------------------

printf("output format:\n");

av_dump_format(ofmt_ctx, 0, out_filename, 1);

// 寫文件頭

ret = avformat_write_header(ofmt_ctx, NULL);

if (ret < 0)

{

printf( "Error occurred when opening output file\n");

goto end;

}

// 幀

while (1)

{

AVStream *in_stream, *out_stream;

//獲取一個AVPacket

ret = av_read_frame(ifmt_ctx, &pkt);

if (ret < 0)

{

printf("av_read_frame failed or end of stream.\n");

break;

}

in_stream = ifmt_ctx->streams[pkt.stream_index];

out_stream = ofmt_ctx->streams[pkt.stream_index];

//轉換PTS/DTS

pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base,

out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));

pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base,

out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));

pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);

pkt.pos = -1;

// 寫入一幀

ret = av_interleaved_write_frame(ofmt_ctx, &pkt);

if (ret < 0) {

printf( "Error muxing packet\n");

break;

}

av_free_packet(&pkt);

}

//寫文件尾(Write file trailer)

printf("--------write trailer------------\n");

av_write_trailer(ofmt_ctx);

// 把輸出的視頻寫到文件中

printf("write to file: %s %p %d\n", out_filename, g_avbuffer_out.ptr, g_avbuffer_out.realSize);

write_file(out_filename, (char*)g_avbuffer_out.ptr, g_avbuffer_out.realSize, 1);

end:

if (avio_in != NULL) av_freep(avio_in);

if (avio_out != NULL) av_freep(avio_out);

//if (g_ptr_in != NULL) free(g_ptr_in);

if (g_ptr_out != NULL) free(g_ptr_out);

// 該函數會釋放用戶自定義的IO buffer,上面不再釋放,否則會corrupted double-linked list

avformat_close_input(&ifmt_ctx);

avformat_free_context(ofmt_ctx);

if (g_avbuffer_in.ptr != NULL) free(g_avbuffer_in.ptr);

if (g_avbuffer_out.ptr != NULL) free(g_avbuffer_out.ptr);

return ret;

}

PS:有人問我為什么在代碼里經常看到LL_DEBUG,實際上“LL”是本人大名。至于解釋為“l(fā)ow level”,那是騙人的。

李遲 2015.8.26 周三 晚,吃項目飯??

總結

以上是生活随笔為你收集整理的java和ffmpeg使用内存转码_FFMPEG基于内存的转码实例的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。