从图像创建视频

9

有没有一种方法可以在安卓上从一系列图像创建视频?或许可以扩展MediaRecorder并能够将图像作为输入。

我尝试着真正地创建视频并储存它(例如,作为mpeg4文件)。

谢谢任何建议。


欢迎来到Stackoverflow!如果某个回答对您有帮助,请点赞。如果该回答成功解答了您的问题,请点击旁边的绿色勾号接受答案。 - Paul Burke
你好。最终你成功地从这些图像中创建了MPEG4格式的视频吗?谢谢。 - Paul
4个回答

7
我也在尝试做同样的事情。我被建议使用Libav。http://libav.org/然而,我需要用NDK构建它,但目前有一些问题。
我在寻找一些关于此的文档。我会随时通报你。
我已经创建了一个关于此的帖子:Libav build for Android

我也在努力实现同样的任务,但不幸的是,我完全不了解ndk、ffmpeg,也没有更好的教程。你能帮我吗?非常感谢任何的帮助。 - Aashish Bhatnagar

1

您可以在ImageView中使用AnimationDrawable

使用AnimationDrawable.addFrame(Drawable frame, int duration)方法添加帧,并使用AnimationDrawable.start()开始动画。

不确定是否理想,但这将起作用。


嗨,iPaulPro,感谢您的回答。 我认为我表达得不够清楚。我试图真正创建视频并将其存储(例如作为mpeg4文件)。AnimationDrawable不会创建任何输出。 - HiddenDev
哦,我明白了。您可能需要编辑您的问题以更好地描述您的需求。 - Paul Burke
1
我很喜欢因为正确回答原问题而被踩的感觉。 - Paul Burke

1
我使用 Android + NDK。
AVFrame* OpenImage(const char* imageFileName)
{
    AVFormatContext *pFormatCtx =  avformat_alloc_context();
    std::cout<<"1"<<imageFileName<<std::endl;
    if( avformat_open_input(&pFormatCtx, imageFileName, NULL, NULL) < 0)
    {
        printf("Can't open image file '%s'\n", imageFileName);
        return NULL;
    }
    std::cout<<"2"<<std::endl;
    av_dump_format(pFormatCtx, 0, imageFileName, false);

    AVCodecContext *pCodecCtx;
    std::cout<<"3"<<std::endl;
    pCodecCtx = pFormatCtx->streams[0]->codec;
    pCodecCtx->width = W_VIDEO;
    pCodecCtx->height = H_VIDEO;
    //pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

    // Find the decoder for the video stream
    AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (!pCodec)
    {
        printf("Codec not found\n");
        return NULL;
    }

    // Open codec
    //if(avcodec_open2(pCodecCtx, pCodec)<0)
    if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)//check this NULL, it should be of AVDictionary **options
    {
        printf("Could not open codec\n");
        return NULL;
    }
    std::cout<<"4"<<std::endl;
    //
    AVFrame *pFrame;

    pFrame = av_frame_alloc();

    if (!pFrame)
    {
        printf("Can't allocate memory for AVFrame\n");
        return NULL;
    }
    printf("here");
    int frameFinished;
    int numBytes;

    // Determine required buffer size and allocate buffer
    numBytes = avpicture_get_size( pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
    uint8_t *buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));

    avpicture_fill((AVPicture *) pFrame, buffer, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

    // Read frame

    AVPacket packet;

    int framesNumber = 0;
    while (av_read_frame(pFormatCtx, &packet) >= 0)
    {
        if(packet.stream_index != 0)
            continue;

        int ret = avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
        if (ret > 0)
        {
            printf("Frame is decoded, size %d\n", ret);
            pFrame->quality = 4;
            return pFrame;
        }
        else
            printf("Error [%d] while decoding frame: %s\n", ret, strerror(AVERROR(ret)));
    }
}
int combine_images_to_video(const char * infile_dir, const char * infile_prefix, const char* infile_surname, int total_frames,const char *outfile)
{


    if (total_frames <= 0){
        std::cout << "Usage: cv2ff <dir_name> <prefix> <image surname> <total frames> <outfile>" << std::endl;
        std::cout << "Please check that the 4th argument is integer value of total frames"<<std::endl;
        return 1;
    }
    printf("max %d frames\n",total_frames);

    char *imageFileName;
    char numberChar[NUMNUMBER];
    // initialize FFmpeg library
    av_register_all();
    //  av_log_set_level(AV_LOG_DEBUG);
    int ret;

    const int dst_width = W_VIDEO;
    const int dst_height = H_VIDEO;
    const AVRational dst_fps = {30, 1};//{fps,1}


    // open output format context
    AVFormatContext* outctx = nullptr;
    ret = avformat_alloc_output_context2(&outctx, nullptr, nullptr, outfile);

    //outctx->video_codec->
    if (ret < 0) {
        std::cerr << "fail to avformat_alloc_output_context2(" << outfile << "): ret=" << ret;
        return 2;
    }

    // open output IO context
    ret = avio_open2(&outctx->pb, outfile, AVIO_FLAG_WRITE, nullptr, nullptr);
    if (ret < 0) {
        std::cerr << "fail to avio_open2: ret=" << ret;
        return 2;
    }
// create new video stream
    AVCodec* vcodec = avcodec_find_encoder(outctx->oformat->video_codec);
    AVStream* vstrm = avformat_new_stream(outctx, vcodec);
    if (!vstrm) {
        std::cerr << "fail to avformat_new_stream";
        return 2;
    }
    avcodec_get_context_defaults3(vstrm->codec, vcodec);
    vstrm->codec->width = dst_width;
    vstrm->codec->height = dst_height;
    vstrm->codec->pix_fmt = vcodec->pix_fmts[0];
    vstrm->codec->time_base = vstrm->time_base = av_inv_q(dst_fps);
    vstrm->r_frame_rate = vstrm->avg_frame_rate = dst_fps;
    if (outctx->oformat->flags & AVFMT_GLOBALHEADER)
        vstrm->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    // open video encoder
    ret = avcodec_open2(vstrm->codec, vcodec, nullptr);
    if (ret < 0) {
        std::cerr << "fail to avcodec_open2: ret=" << ret;
        return 2;
    }

    std::cout
            << "outfile: " << outfile << "\n"
            << "format:  " << outctx->oformat->name << "\n"
            << "vcodec:  " << vcodec->name << "\n"
            << "size:    " << dst_width << 'x' << dst_height << "\n"
            << "fps:     " << av_q2d(dst_fps) << "\n"
            << "pixfmt:  " << av_get_pix_fmt_name(vstrm->codec->pix_fmt) << "\n"
            << std::flush;

    // initialize sample scaler
    SwsContext* swsctx = sws_getCachedContext(
            nullptr, dst_width, dst_height, AV_PIX_FMT_BGR24,
            dst_width, dst_height, vstrm->codec->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr);
    if (!swsctx) {
        std::cerr << "fail to sws_getCachedContext";
        return 2;
    }

    // allocate frame buffer for encoding
    AVFrame* frame = av_frame_alloc();
    std::vector<uint8_t> framebuf(avpicture_get_size(vstrm->codec->pix_fmt, dst_width, dst_height));
    avpicture_fill(reinterpret_cast<AVPicture*>(frame), framebuf.data(), vstrm->codec->pix_fmt, dst_width, dst_height);
    frame->width = dst_width;
    frame->height = dst_height;
    frame->format = static_cast<int>(vstrm->codec->pix_fmt);

    // encoding loop
    avformat_write_header(outctx, nullptr);
    int64_t frame_pts = 0;
    unsigned nb_frames = 0;
    bool end_of_stream = false;
    int got_pkt = 0;
    int i =0;
    imageFileName = (char *)malloc(strlen(infile_dir)+strlen(infile_prefix)+NUMNUMBER+strlen(infile_surname)+1);
    do{
        if(!end_of_stream){

            strcpy(imageFileName,infile_dir);
            //strcat(imageFileName,"/");
            strcat(imageFileName,infile_prefix);
            sprintf(numberChar,"%03d",i+1);
            strcat(imageFileName,numberChar);
            //strcat(imageFileName,".");
            strcat(imageFileName,infile_surname);
            __android_log_print(1, "RecordingImage", "%s", imageFileName);
            std::cout<<imageFileName<<std::endl;


            AVFrame* frame_from_file =  OpenImage(imageFileName);
            if(!frame_from_file){
                std::cout<<"error OpenImage"<<std::endl;
                return 5;
            }
            //const int Stride [] = {1920};
            sws_scale(swsctx, frame_from_file->data, &STRIDE , 0, frame_from_file->height, frame->data, frame->linesize);
            frame->pts = frame_pts++;
            av_frame_free(&frame_from_file);
        }
        // encode video frame
        AVPacket pkt;
        pkt.data = nullptr;
        pkt.size = 0;
        av_init_packet(&pkt);
        ret = avcodec_encode_video2(vstrm->codec, &pkt, end_of_stream ? nullptr : frame, &got_pkt);
        if (ret < 0) {
            std::cerr << "fail to avcodec_encode_video2: ret=" << ret << "\n";
            return 2;
        }

        // rescale packet timestamp
        pkt.duration = 1;
        av_packet_rescale_ts(&pkt, vstrm->codec->time_base, vstrm->time_base);
        // write packet
        av_write_frame(outctx, &pkt);
        std::cout << nb_frames << '\r' << std::flush;  // dump progress
        ++nb_frames;

        av_free_packet(&pkt);
        i++;
        if(i==total_frames-1)
            end_of_stream = true;
    } while (i<total_frames);

    av_write_trailer(outctx);
    std::cout << nb_frames << " frames encoded" << std::endl;

    av_frame_free(&frame);
    avcodec_close(vstrm->codec);
    avio_close(outctx->pb);
    avformat_free_context(outctx);
    free(imageFileName);


    return 0;
}

0

我们可以使用ffmpeg从图像创建视频。

查看我的post,了解如何在Android中使用ffmpeg。

使用以下命令从放置在同一文件夹中的图像创建视频

String command[]={"-y", "-r","1/5" ,"-i",src.getAbsolutePath(),
"-c:v","libx264","-vf", "fps=25","-pix_fmt","yuv420p", dest.getAbsolutePath()};

在这里,

src.getAbsolutePath() 是您所有输入图像的绝对路径。

例如, 如果您的所有图像都存储在Pictures目录下的Images文件夹中,且命名为 extract_picture001.jpg, extract_picture002.jpg, extract_picture003.jpg......

那么,

String filePrefix = "extract_picture";
String fileExtn = ".jpg";
File picDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File dir = new File(picDir, "Images");
File src = new File(dir, filePrefix + "%03d" + fileExtn);

如果要从不同文件夹中放置的图像创建视频,则需要创建一个文本文件并将图像路径添加到其中,然后将该文本文件的路径指定为输入选项。

例如:

文本文件

file '/storage/emulated/0/DCIM/Camera/P_20170807_143916.jpg'
duration 2
file '/storage/emulated/0/DCIM/Pic/P_20170305_142948.jpg'
duration 5
file '/storage/emulated/0/DCIM/Camera/P_20170305_142939.jpg'
duration 6
file '/storage/emulated/0/DCIM/Pic/P_20170305_142818.jpg'
duration 2

命令

String command[] = {"-y", "-f", "concat", "-safe", "0", "-i", textFile.getAbsolutePath(), "-vsync", "vfr", "-pix_fmt", "yuv420p", dest.getAbsolutePath()};

其中textFile.getAbsolutePath()是您的文本文件的绝对路径

查看此ffmpeg doc 以获取更多信息


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接