파일 복호화와 동시에 스트리밍 할 때, 멀티스레딩에 대한 질문입니다.

조회수 725회
  • 저는 지금 파일을 복호화해서 재생하는 기능을 만들고 있습니다. Http 프록시 로컬 서버를 마련해 두고 파일을 복호화 하면서 동시에 스트리밍하고 있는데요.

  • 문제는 다음과 같습니다. 파일 복호화가 완료되지 않으면 플레이어의 SeekBar로 탐색을 할 수가 없습니다. Skip이 되지 않는 것인데요. 지금은 프로그레스를 띄어서 복호화가 완료되기 전까지는 Seekbar를 사용할 수 없도록 했습니다. 계속 이런식으로 하다간 사용자 저항이 심할게 뻔합니다. 특히 대용량 동영상 파일이라면 확실하죠...

  • 그래서 제가 생각한 방식은 복호화를 최대한 빠르게하자 입니다. 복호화를 담당하는 쓰레드를 여러개 두고 분할 복호화를 하는 것이죠. 마지막엔 물론 파일을 합쳐야 합니다. 파일의 순서대로 4096 바이트 마다 복호화를 해야합니다. 그리고 스트리밍을 해야 하기 때문에 복호화 하면서 재생이 되야하는데 절차가 복잡하네요. 멀티스레드로 돌렸을 때도 스트리밍이 될까요.. 이 방식이 과연 제일 효율적일까요?

  • 이 문제를 해결할 가장 좋은 방식은 무엇일까요? (ExoPlayer제외)

복호화 스레드와 서버 스레드를 만드는 코드입니다.

public class VideoDownloadAndPlayService
{
    private static final String TAG = "VideoDownloadAndPlaySer";
    private static LocalFileStreamingServer server;
    private static VideoDownloader videoDownloader;

    private VideoDownloadAndPlayService(LocalFileStreamingServer server)
    {
        this.server = server;
    }

    public static VideoDownloadAndPlayService startServer(final Activity activity, EnDecryptVideo enDecryptVideo, String videoUrl, String videoId, String pathToSaveVideo, final String ipOfServer, final VideoStreamInterface callback)
    {
        videoDownloader = (VideoDownloader) new VideoDownloader(activity, enDecryptVideo).execute(videoUrl, videoId);
        server = new LocalFileStreamingServer(new File(pathToSaveVideo, videoId + ".mp4"));
        server.setSupportPlayWhileDownloading(true);
        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                server.init(ipOfServer);

                activity.runOnUiThread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        server.start();
                        callback.onServerStart(server.getFileUrl());
                    }
                });
            }
        }).start();

        return new VideoDownloadAndPlayService(server);
    }

    public void start(){
        server.start();
    }

    public void stop(){
        server.stop();
    }

    public void stopDownloader() { videoDownloader.cancel(true); }

    public static interface VideoStreamInterface{
        public void onServerStart(String videoStreamUrl);
    }
}

다음은 복호화 하는 스레드 입니다.

public class VideoDownloader extends AsyncTask<String, Integer, Void> {

    private static final String TAG = "VideoDownloader";
    public static final int DATA_READY = 1;
    public static final int DATA_NOT_READY = 2;
    public static final int DATA_CONSUMED = 3;
    public static final int DATA_NOT_AVAILABLE = 4;
    private Context context;
    private EnDecryptVideo enDecryptVideo;

    public static int dataStatus = -1;

    public VideoDownloader(Context context, EnDecryptVideo enDecryptVideo) {
        Log.d(TAG, "VideoDownloader: " + "Async Ready");
        this.context = context;
        this.enDecryptVideo = enDecryptVideo;
    }

    public static boolean isDataReady() {
        dataStatus = -1;
        boolean res = false;
        if (fileLength == readb) {
            dataStatus = DATA_CONSUMED;
            res = false;
        } else if (readb > consumedb) {
            dataStatus = DATA_READY;
            res = true;
        } else if (readb <= consumedb) {
            dataStatus = DATA_NOT_READY;
            res = false;
        } else if (fileLength == -1) {
            dataStatus = DATA_NOT_AVAILABLE;
            res = false;
        }
        return res;
    }

    /**
     * Keeps track of read bytes while serving to video player client from server
     */
    public static int consumedb = 0;

    /**
     * Keeps track of all bytes read on each while iteration
     */
    private static long readb = 0;

    /**
     * Length of file being downloaded.
     */
    static long fileLength = -1;

    @Override
    protected Void doInBackground(String... params) {
        long startTime = 0;
        FileInputStream ios = null;
        FileOutputStream fos = null;

        try {
            ios = new FileInputStream(params[0]);
            fos = context.openFileOutput(params[1] + ".mp4", MODE_PRIVATE);

            ScatteringByteChannel sbc = ios.getChannel();
            GatheringByteChannel gbc = fos.getChannel();

            File file = new File(params[0]);
            fileLength = file.length();

            startTime = System.currentTimeMillis();
            int read = 0;
            readb = 0;
            ByteBuffer bb = ByteBuffer.allocate(4096);
            while ((read = sbc.read(bb)) != -1) {
                bb.flip();
                gbc.write(ByteBuffer.wrap(enDecryptVideo.combineByteArray(bb.array())));
                bb.clear();
                readb += read;
                if (readb % (4096  * 1024 * 3) == 0){
                    publishProgress(((int) ( readb * 100 / fileLength)));
                } else if (readb == fileLength) {
                    publishProgress(101);
                }
            }
            ios.close();
            fos.close();
        } catch (Exception e) {
            e.getMessage();
        } finally {
            Log.d(TAG, "doInBackground: " + (System.currentTimeMillis() - startTime));
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        int progress = values[0];
        ((VideoActivity)context).onProgressResult(values[0]);
        Log.d(TAG, "onProgressUpdate: " + progress);

    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        Log.w("download", "Done");
    }
}
  • (•́ ✖ •̀)
    알 수 없는 사용자
  • 시나리오만 읽었을 때 떠오르는 의문은 "파일 복호화를 구간 나눠서 할 수 있단 말인가?"인데요. 처음부터 암호화 자체가 구간별로 되어 있었다면 모를까 암호화/복호화라는 건 언제나 대상 '전체'에 대해서 수행하는 거거든요. 엽토군 2018.7.19 15:44
  • 그리고 멀티스레드가 파일의 일부를 분담해 가져가서 처리하고 돌려주려면 이어붙이는 순서가 올바르게 되어야 할 텐데 그 부분 처리돼 있나요? 엽토군 2018.7.19 15:47
  • 복호화를 4096 바이트씩 읽으면서 암호화를 진행합니다. 4096 바이트 단위로 나눌 수 있다면 구간 복호화할 수 있지요. 물론 파일 전체에 대해서 암호화와 복호화를 진행하지요. 알 수 없는 사용자 2018.7.19 15:47
  • 그 부분도 처리해야합니다. 알 수 없는 사용자 2018.7.19 16:20

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)